import vulkan_funcs; import vulkan_logging; import aliases; import std.stdio; import std.algorithm.comparison; import core.stdc.string : strcmp; import std.format : sformat; import u = util : HashTable, Result, Logf, Log; import a = alloc; import p = platform; import renderer; bool g_VLAYER_SUPPORT = false; const FRAME_OVERLAP = 2; version(linux) { const string[] VULKAN_LIBS = [ "libvulkan.so.1", "libvulkan.so", ]; } version(Windows) { const string[] VULKAN_LIBS = [ "vulkan-1.dll", ]; } const char*[] VK_INSTANCE_LAYERS = []; const char*[] VK_INSTANCE_LAYERS_DEBUG = [ "VK_LAYER_KHRONOS_validation" ]; version(linux) { const char*[] VK_INSTANCE_EXT = [ cast(char*)VK_KHR_SURFACE_EXTENSION_NAME, cast(char*)VK_KHR_XCB_SURFACE_EXTENSION_NAME ]; const char*[] VK_INSTANCE_EXT_DEBUG = VK_INSTANCE_EXT ~ [ cast(char*)VK_EXT_DEBUG_UTILS_EXTENSION_NAME ]; } version(Windows) { const char*[] VK_INSTANCE_EXT = [ cast(char*)VK_KHR_SURFACE_EXTENSION_NAME, cast(char*)VK_KHR_WIN32_SURFACE_EXTENSION_NAME ]; const char*[] VK_INSTANCE_EXT_DEBUG = VK_INSTANCE_EXT ~ [ cast(char*)VK_EXT_DEBUG_UTILS_EXTENSION_NAME ]; } const char*[] VK_DEVICE_EXTENSIONS = [ cast(char*)VK_KHR_SWAPCHAIN_EXTENSION_NAME, cast(char*)VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME, ]; const VkFormat[] VK_IMAGE_FORMATS = [ VK_FORMAT_R16G16B16A16_UNORM, VK_FORMAT_R8G8B8A8_UNORM, ]; const VkImageUsageFlags VK_DRAW_IMAGE_USAGE_FLAGS = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; enum StepInitialized : u32 { Renderer = 1, Instance, Debug, Surface, Device, Vma, MappedBuffers, // TODO FrameStructures, Swapchain, DrawImages, Descriptors, } alias SI = StepInitialized; enum DescType : u32 { Shared = 0, SampledImage, Material, Mesh, Max, } alias DT = DescType; struct PushConst { u32 texture; } struct Image { VkImage image; VmaAllocation alloc; VkFormat format; VkImageLayout layout; } struct ImageView { Image base; VkImageView view; } struct DescBindings { u32[] free; u64 count; HashTable!(string, u32) lookup_table; } struct Vulkan { a.Arena arena; a.Arena[FRAME_OVERLAP] frame_arenas; u.SLList!(SI) cleanup_list; p.Window* window; VkDebugUtilsMessengerEXT dbg_msg; VkInstance instance; VkSurfaceKHR surface; VkPhysicalDevice physical_device; VkDevice device; VmaAllocator vma; VkSwapchainKHR swapchain; VkSurfaceFormatKHR surface_format; VkPresentModeKHR present_mode; VkExtent3D swapchain_extent; ImageView[] present_images; ImageView draw_image; ImageView depth_image; VkCommandPool[FRAME_OVERLAP] cmd_pools; VkCommandBuffer[FRAME_OVERLAP] cmds; VkSemaphore[] swapchain_sems; VkSemaphore[FRAME_OVERLAP] render_sems; VkFence[FRAME_OVERLAP] render_fences; VkCommandPool imm_pool; VkCommandBuffer imm_cmd; VkFence imm_fence; VkDescriptorPool desc_pool; VkDescriptorSet[DT.max] desc_sets; VkDescriptorSetLayout[DT.max] desc_layouts; DescBindings[DT.max] desc_bindings; VkSampler nearest_sampler; VkPipelineLayout pipeline_layout; QueueInfo queues; } struct QueueInfo { i32 gfx_index, tfer_index; VkQueue gfx_queue, tfer_queue; bool single_queue; }; u.Result!(Vulkan) Init(p.Window* window, u64 permanent_mem, u64 frame_mem) { bool success = true; Vulkan vk = { arena: a.CreateArena(permanent_mem), frame_arenas: [ a.CreateArena(frame_mem), a.CreateArena(frame_mem), ], window: window, }; Push(&vk, SI.Renderer); success = LoadGlobalFunctions(); if (success) success = InitInstance(&vk); if (success) { LoadInstanceFunctions(&vk); EnableVLayers(&vk); } if (success) success = InitSurface(&vk); if (success) success = InitDevice(&vk); if (success) success = InitVMA(&vk); if (success) success = CreateSwapchain(&vk); if (success) success = CreateDrawImages(&vk); if (success) success = InitFrameStructures(&vk); if (success) success = InitDescriptors(&vk); u.Result!(Vulkan) result = { ok: success, value: vk, }; return result; } Result!(Shader) BuildShader(Vulkan* vk, u8[] bytes) { Result!(Shader) shader; VkShaderModuleCreateInfo shader_info = { sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, codeSize: bytes.length, pCode: cast(uint*)bytes.ptr, }; VkResult result = vkCreateShaderModule(vk.device, &shader_info, null, &shader.value); shader.ok = VkCheck("vkCreateShaderModule failure", result); return shader; } Result!(VkPipeline) CreateComputePipeline(Vulkan* vk, string shader) { Result!(VkPipeline) pipeline; return pipeline; } void Destroy(Vulkan* vk) { alias N = u.Node!(SI); assert(vk.cleanup_list.first != null, "node null"); for(N* node = vk.cleanup_list.first; node != null; node = node.next) { switch (node.value) { case SI.Renderer: DestroyRenderer(vk); break; case SI.Instance: Destroy(vk.instance); break; case SI.Debug: Destroy(vk.dbg_msg, vk.instance); break; case SI.Surface: Destroy(vk.surface, vk.instance); break; case SI.Device: Destroy(vk.device); break; case SI.Vma: Destroy(vk.vma); break; case SI.FrameStructures: DestroyFS(vk); break; case SI.Swapchain: Destroy(vk.swapchain, vk.present_images, vk.device); break; case SI.DrawImages: Destroy(&vk.draw_image, &vk.depth_image, vk.device, vk.vma); break; case SI.Descriptors: Destroy(vk.desc_pool, vk.desc_layouts, vk.pipeline_layout, vk.nearest_sampler, vk.device); break; default: break; } } } bool InitDescriptors(Vulkan* vk) { Push(vk, SI.Descriptors); bool success = true; VkDescriptorPoolSize[] pool_sizes = [ { type: VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, descriptorCount: 4096 }, { type: VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, descriptorCount: 4096 }, { type: VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, descriptorCount: 4096 }, { type: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, descriptorCount: 4096 }, { type: VK_DESCRIPTOR_TYPE_SAMPLER, descriptorCount: 4096 }, ]; VkDescriptorPoolCreateInfo pool_info = { sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, poolSizeCount: cast(u32)pool_sizes.length, pPoolSizes: pool_sizes.ptr, maxSets: 12, flags: VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT, }; VkResult result = vkCreateDescriptorPool(vk.device, &pool_info, null, &vk.desc_pool); success = VkCheck("vkCreateDescriptorPool failure", result); if (success) { VkDescriptorBindingFlags[4] shared_binding_flags; shared_binding_flags[] = VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT; VkDescriptorSetLayoutBindingFlagsCreateInfo shared_flag_info = { sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, bindingCount: cast(u32)shared_binding_flags.length, pBindingFlags: shared_binding_flags.ptr, }; VkDescriptorSetLayoutBinding[] shared_bindings = [ { binding: 0, descriptorType: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, descriptorCount: 1, stageFlags: VK_SHADER_STAGE_ALL }, { binding: 1, descriptorType: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, descriptorCount: 1, stageFlags: VK_SHADER_STAGE_ALL }, { binding: 2, descriptorType: VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, descriptorCount: 1, stageFlags: VK_SHADER_STAGE_ALL }, { binding: 3, descriptorType: VK_DESCRIPTOR_TYPE_SAMPLER, descriptorCount: 1, stageFlags: VK_SHADER_STAGE_ALL }, ]; VkDescriptorSetLayoutCreateInfo shared_set_info = { sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, pNext: &shared_flag_info, bindingCount: cast(u32)shared_bindings.length, pBindings: shared_bindings.ptr, flags: VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT, }; result = vkCreateDescriptorSetLayout(vk.device, &shared_set_info, null, &vk.desc_layouts[DT.Shared]); success = VkCheck("vkCreateDescriptorSetLayout failure", result); } if (success) { VkDescriptorType[DT.max] type_lookup = [ DT.SampledImage: VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, DT.Material: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, DT.Mesh: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, ]; VkDescriptorBindingFlags bindless_flag = VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT | VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT; VkDescriptorSetLayoutBindingFlagsCreateInfo bindless_flag_info = { sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, bindingCount: 1, pBindingFlags: &bindless_flag, }; VkDescriptorSetLayoutBinding binding = { binding: 0, descriptorCount: 1024, stageFlags: VK_SHADER_STAGE_ALL, }; VkDescriptorSetLayoutCreateInfo bindless_set_info = { sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, pNext: &bindless_flag_info, bindingCount: 1, pBindings: &binding, flags: VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT, }; foreach(i; cast(u64)(DT.Shared+1) .. DT.max) { binding.descriptorType = type_lookup[i]; if (success) { result = vkCreateDescriptorSetLayout(vk.device, &bindless_set_info, null, vk.desc_layouts.ptr + i); success = VkCheck("vkCreateDescriptorSetLayout failure", result); } } } if (success) { VkDescriptorSetAllocateInfo alloc_info = { sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, descriptorSetCount: cast(u32)DT.max, pSetLayouts: vk.desc_layouts.ptr, descriptorPool: vk.desc_pool, }; result = vkAllocateDescriptorSets(vk.device, &alloc_info, vk.desc_sets.ptr); success = VkCheck("vkAllocateDescriptorSets failure", result); } if (success) { VkPushConstantRange const_range = { offset: 0, size: cast(VkDeviceSize)PushConst.sizeof, stageFlags: VK_SHADER_STAGE_COMPUTE_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT, }; VkPipelineLayoutCreateInfo layout_info = { sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, setLayoutCount: cast(u32)DT.max, pushConstantRangeCount: 1, pPushConstantRanges: &const_range, pSetLayouts: vk.desc_layouts.ptr, }; result = vkCreatePipelineLayout(vk.device, &layout_info, null, &vk.pipeline_layout); success = VkCheck("vkCreatePipelineLayout failure", result); } if (success) { foreach(i; cast(u64)DT.min .. cast(u64)DT.max) { vk.desc_bindings[i].lookup_table = u.CreateHashTable!(string, u32)(8); u32 DESC_MAX_BINDINGS = 512; vk.desc_bindings[i].free = a.AllocArray!(u32)(&vk.arena, DESC_MAX_BINDINGS); u32 free_count = 0; for(i32 j = DESC_MAX_BINDINGS-1; j >= 0; j -= 1) { vk.desc_bindings[i].free[j] = cast(u32)free_count; free_count += 1; } vk.desc_bindings[i].count = free_count; } } if (success) { VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(vk.physical_device, &props); VkSamplerCreateInfo sampler_info = { sType: VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, magFilter: VK_FILTER_NEAREST, minFilter: VK_FILTER_NEAREST, 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, compareOp: VK_COMPARE_OP_ALWAYS, mipmapMode: VK_SAMPLER_MIPMAP_MODE_LINEAR, }; result = vkCreateSampler(vk.device, &sampler_info, null, &vk.nearest_sampler); success = VkCheck("vkCreateSampler failure", result); } if (success) { VkDescriptorImageInfo sampler_info = { sampler: vk.nearest_sampler, }; VkWriteDescriptorSet write = { sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, dstSet: vk.desc_sets[DT.Shared], dstBinding: 3, descriptorCount: 1, descriptorType: VK_DESCRIPTOR_TYPE_SAMPLER, pImageInfo: &sampler_info, }; vkUpdateDescriptorSets(vk.device, 1, &write, 0, null); } return success; } bool VkCheck(string message, VkResult result) { bool success = true; if (result != VK_SUCCESS) { success = false; char[512] buf; buf.sformat("%s: %s", message, VkResultStr(result)); Log(buf.ptr); } return success; } bool InitFrameStructures(Vulkan* vk) { Push(vk, SI.FrameStructures); bool success = true; a.Arena* arena = &vk.frame_arenas[0]; VkSemaphoreCreateInfo sem_info = { sType: VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; VkCommandPoolCreateInfo pool_info = { sType: VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, flags: VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, }; VkFenceCreateInfo fence_info = { sType: VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, flags: VK_FENCE_CREATE_SIGNALED_BIT, }; VkCommandBufferAllocateInfo cmd_info = { sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, commandBufferCount: 1, level: VK_COMMAND_BUFFER_LEVEL_PRIMARY, }; vk.swapchain_sems = a.AllocArray!(VkSemaphore)(arena, vk.present_images.length); foreach(i; 0 .. vk.swapchain_sems.length) { VkResult result = vkCreateSemaphore(vk.device, &sem_info, null, vk.swapchain_sems.ptr + i); success = VkCheck("vkCreateSemaphore failure", result); } foreach(i; 0 .. FRAME_OVERLAP) { VkResult result; if (success) { result = vkCreateCommandPool(vk.device, &pool_info, null, vk.cmd_pools.ptr + i); success = VkCheck("vkCreateCommandPool failure", result); } if (success) { cmd_info.commandPool = vk.cmd_pools[i]; result = vkAllocateCommandBuffers(vk.device, &cmd_info, vk.cmds.ptr + i); success = VkCheck("vkAllocateCommandBuffers failure", result); } if (success) { result = vkCreateFence(vk.device, &fence_info, null, vk.render_fences.ptr + i); success = VkCheck("vkCreateFence failure", result); } if (success) { result = vkCreateSemaphore(vk.device, &sem_info, null, vk.render_sems.ptr + i); success = VkCheck("vkCreateSemaphore failure", result); } } if (success) { VkResult result = vkCreateCommandPool(vk.device, &pool_info, null, &vk.imm_pool); success = VkCheck("vkCreateCommandPool failure", result); } if (success) { cmd_info.commandPool = vk.imm_pool; VkResult result = vkAllocateCommandBuffers(vk.device, &cmd_info, &vk.imm_cmd); success = VkCheck("vkAllocateCommandBuffers failure", result); } if (success) { VkResult result = vkCreateFence(vk.device, &fence_info, null, &vk.imm_fence); success = VkCheck("vkCreateFence failure", result); } return success; } VkFormat GetDrawImageFormat(Vulkan* vk) { VkFormat selected_format; foreach(format; VK_IMAGE_FORMATS) { VkImageFormatProperties props; VkResult result = vkGetPhysicalDeviceImageFormatProperties( vk.physical_device, format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, VK_DRAW_IMAGE_USAGE_FLAGS, 0, &props ); if (result == VK_ERROR_FORMAT_NOT_SUPPORTED) { continue; } if (result == VK_SUCCESS) { selected_format = format; break; } } return selected_format; } bool CreateDrawImages(Vulkan* vk) { Push(vk, SI.DrawImages); bool success = true; VkFormat draw_format = GetDrawImageFormat(vk); VkFormat depth_format = VK_FORMAT_D32_SFLOAT; VmaAllocationCreateInfo alloc_info = { usage: VMA_MEMORY_USAGE_GPU_ONLY, requiredFlags: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, }; VkImageCreateInfo image_info = { sType: VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, imageType: VK_IMAGE_TYPE_2D, mipLevels: 1, arrayLayers: 1, samples: VK_SAMPLE_COUNT_1_BIT, tiling: VK_IMAGE_TILING_OPTIMAL, usage: VK_DRAW_IMAGE_USAGE_FLAGS, extent: vk.swapchain_extent, format: draw_format, }; VkResult result = vmaCreateImage(vk.vma, &image_info, &alloc_info, &vk.draw_image.base.image, &vk.draw_image.base.alloc, null); success = VkCheck("vmaCreateImage failure", result); if (success) { VkImageViewCreateInfo view_info = { sType: VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, image: vk.draw_image.base.image, format: draw_format, viewType: VK_IMAGE_VIEW_TYPE_2D, subresourceRange: { aspectMask: VK_IMAGE_ASPECT_COLOR_BIT, baseMipLevel: 0, levelCount: 1, baseArrayLayer: 0, layerCount: 1, }, }; result = vkCreateImageView(vk.device, &view_info, null, &vk.draw_image.view); success = VkCheck("vkCreateImageView failure", result); } if (success) { VkImageCreateInfo depth_image_info = { sType: VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, imageType: VK_IMAGE_TYPE_2D, mipLevels: 1, arrayLayers: 1, samples: VK_SAMPLE_COUNT_1_BIT, tiling: VK_IMAGE_TILING_OPTIMAL, format: depth_format, usage: VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, extent: vk.swapchain_extent, }; result = vmaCreateImage(vk.vma, &depth_image_info, &alloc_info, &vk.depth_image.base.image, &vk.depth_image.base.alloc, null); success = VkCheck("vmaCreateImage failure", result); } if (success) { VkImageViewCreateInfo depth_view_info = { sType: VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, image: vk.depth_image.base.image, viewType: VK_IMAGE_VIEW_TYPE_2D, format: depth_format, subresourceRange: { aspectMask: VK_IMAGE_ASPECT_DEPTH_BIT, baseMipLevel: 0, levelCount: 1, baseArrayLayer: 0, layerCount: 1, }, }; result = vkCreateImageView(vk.device, &depth_view_info, null, &vk.depth_image.view); success = VkCheck("vmaCreateImageView failure", result); } vk.draw_image.base.format = draw_format; vk.draw_image.base.layout = VK_IMAGE_LAYOUT_UNDEFINED; vk.depth_image.base.format = depth_format; vk.depth_image.base.layout = VK_IMAGE_LAYOUT_UNDEFINED; return success; } void SelectSwapchainFormats(Vulkan* vk) { a.Arena* arena = &vk.frame_arenas[0]; u32 format_count; vkGetPhysicalDeviceSurfaceFormatsKHR(vk.physical_device, vk.surface, &format_count, null); VkSurfaceFormatKHR[] formats = a.AllocArray!(VkSurfaceFormatKHR)(arena, format_count); vkGetPhysicalDeviceSurfaceFormatsKHR(vk.physical_device, vk.surface, &format_count, formats.ptr); u32 mode_count; vkGetPhysicalDeviceSurfacePresentModesKHR(vk.physical_device, vk.surface, &mode_count, null); VkPresentModeKHR[] modes = a.AllocArray!(VkPresentModeKHR)(arena, mode_count); vkGetPhysicalDeviceSurfacePresentModesKHR(vk.physical_device, vk.surface, &mode_count, modes.ptr); VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR; foreach(mode; modes) { if (mode == VK_PRESENT_MODE_MAILBOX_KHR) { present_mode = VK_PRESENT_MODE_MAILBOX_KHR; break; } } vk.surface_format = formats[0]; vk.present_mode = present_mode; } bool CreateSwapchain(Vulkan* vk) { Push(vk, SI.Swapchain); bool success = true; a.Arena* arena = &vk.frame_arenas[0]; VkSurfaceCapabilitiesKHR cap; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk.physical_device, vk.surface, &cap); static bool initialized = false; if (!initialized) { SelectSwapchainFormats(vk); } VkSwapchainCreateInfoKHR info = { sType: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, imageArrayLayers: 1, imageUsage: VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, compositeAlpha: VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, clipped: VK_TRUE, imageSharingMode: VK_SHARING_MODE_EXCLUSIVE, minImageCount: cap.minImageCount + 1, surface: vk.surface, imageFormat: vk.surface_format.format, imageColorSpace: vk.surface_format.colorSpace, imageExtent: { width: clamp(cast(u32)vk.window.w, cap.minImageExtent.width, cap.maxImageExtent.width), height: clamp(cast(u32)vk.window.h, cap.minImageExtent.height, cap.maxImageExtent.height), }, preTransform: cap.currentTransform, presentMode: vk.present_mode, }; VkResult result = vkCreateSwapchainKHR(vk.device, &info, null, &vk.swapchain); success = VkCheck("vkCreateSwapchainKHR failure", result); u32 count; vkGetSwapchainImagesKHR(vk.device, vk.swapchain, &count, null); VkImage[] images = a.AllocArray!(VkImage)(arena, count); vkGetSwapchainImagesKHR(vk.device, vk.swapchain, &count, images.ptr); VkImageView[] views = a.AllocArray!(VkImageView)(arena, count); vk.present_images = a.AllocArray!(ImageView)(&vk.arena, count); VkImageViewCreateInfo view_info = { sType: VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, viewType: VK_IMAGE_VIEW_TYPE_2D, components: { r: VK_COMPONENT_SWIZZLE_IDENTITY, g: VK_COMPONENT_SWIZZLE_IDENTITY, b: VK_COMPONENT_SWIZZLE_IDENTITY, a: VK_COMPONENT_SWIZZLE_IDENTITY, }, subresourceRange: { aspectMask: VK_IMAGE_ASPECT_COLOR_BIT, baseMipLevel: 0, levelCount: 1, baseArrayLayer: 0, layerCount: 1, }, }; foreach(i, image; vk.present_images) { vk.present_images[i].base.image = images[i]; view_info.image = images[i]; view_info.format = vk.surface_format.format; result = vkCreateImageView(vk.device, &view_info, null, &vk.present_images[i].view); success = VkCheck("vkCreateImageView failure", result); } vk.swapchain_extent.width = info.imageExtent.width; vk.swapchain_extent.height = info.imageExtent.height; vk.swapchain_extent.depth = 1; if (!initialized && success) { initialized = true; } return success; } bool InitVMA(Vulkan* vk) { Push(vk, SI.Vma); bool success = true; VmaVulkanFunctions vk_functions = { vkGetInstanceProcAddr: vkGetInstanceProcAddr, vkGetDeviceProcAddr: vkGetDeviceProcAddr, }; VmaAllocatorCreateInfo info = { flags: VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT, vulkanApiVersion: VK_MAKE_API_VERSION(0, 1, 3, 0), pVulkanFunctions: &vk_functions, physicalDevice: vk.physical_device, device: vk.device, instance: vk.instance, }; VkResult result = vmaCreateAllocator(&info, &vk.vma); success = VkCheck("vmaCreateAllocator failure", result); return success; } bool InitDevice(Vulkan* vk) { Push(vk, SI.Device); bool success = false; a.Arena* arena = &vk.frame_arenas[0]; u32 count; vkEnumeratePhysicalDevices(vk.instance, &count, null); VkPhysicalDevice[] devices = a.AllocArray!(VkPhysicalDevice)(arena, count); vkEnumeratePhysicalDevices(vk.instance, &count, devices.ptr); VkPhysicalDevice physical_device = null; bool discrete_candidate = false; QueueInfo candidate = { gfx_index: -1, tfer_index: -1, single_queue: false, }; foreach(dev; devices) { QueueInfo current = CheckQueueProperties(arena, dev, vk.surface); b32 discrete = false; if (current.gfx_index < 0) continue; if (!CheckDeviceProperties(arena, dev, vk.surface, &discrete)) continue; if (discrete_candidate && !discrete) continue; if (!CheckDeviceFeatures(dev)) continue; discrete_candidate = cast(bool)discrete; candidate = current; physical_device = dev; if (discrete_candidate && !candidate.single_queue) continue; } if (physical_device) { VkDeviceQueueCreateInfo[2] queue_info; f32 priority = 1.0f; count = 1; queue_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queue_info[0].queueFamilyIndex = candidate.gfx_index; queue_info[0].queueCount = 1; queue_info[0].pQueuePriorities = &priority; queue_info[0].flags = 0; if (!candidate.single_queue) { queue_info[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queue_info[1].queueFamilyIndex = candidate.tfer_index; queue_info[1].queueCount = 1; queue_info[1].pQueuePriorities = &priority; queue_info[1].flags = 0; count += 1; } VkPhysicalDeviceVulkan13Features features_13 = { sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, synchronization2: VK_TRUE, dynamicRendering: VK_TRUE, }; VkPhysicalDeviceVulkan12Features features_12 = { sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, pNext: &features_13, descriptorIndexing: VK_TRUE, bufferDeviceAddress: VK_TRUE, descriptorBindingUniformBufferUpdateAfterBind: VK_TRUE, descriptorBindingSampledImageUpdateAfterBind: VK_TRUE, descriptorBindingStorageImageUpdateAfterBind: VK_TRUE, descriptorBindingStorageBufferUpdateAfterBind: VK_TRUE, descriptorBindingPartiallyBound: VK_TRUE, shaderSampledImageArrayNonUniformIndexing: VK_TRUE, shaderUniformBufferArrayNonUniformIndexing: VK_TRUE, runtimeDescriptorArray: VK_TRUE, }; VkPhysicalDeviceVulkan11Features features_11 = { sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES, pNext: &features_12, }; VkPhysicalDeviceFeatures2 features = { sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, pNext: &features_11, features: { shaderUniformBufferArrayDynamicIndexing: VK_TRUE, shaderSampledImageArrayDynamicIndexing: VK_TRUE, shaderStorageBufferArrayDynamicIndexing: VK_TRUE, shaderStorageImageArrayDynamicIndexing: VK_TRUE, samplerAnisotropy: VK_TRUE, }, }; VkDeviceCreateInfo device_info = { sType: VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, pNext: &features, ppEnabledExtensionNames: VK_DEVICE_EXTENSIONS.ptr, enabledExtensionCount: cast(u32)VK_DEVICE_EXTENSIONS.length, queueCreateInfoCount: count, pQueueCreateInfos: queue_info.ptr, pEnabledFeatures: null, }; VkResult result = vkCreateDevice(physical_device, &device_info, null, &vk.device); if (result != VK_SUCCESS) { Logf("vkCreateDevices failure: %s", VkResultStr(result)); } else { LoadDeviceFunctions(vk); vkGetDeviceQueue( vk.device, candidate.gfx_index, 0, &candidate.gfx_queue ); if (!candidate.single_queue) { vkGetDeviceQueue( vk.device, candidate.tfer_index, candidate.tfer_index == candidate.gfx_index ? 1 : 0, &candidate.tfer_queue ); } else { candidate.tfer_queue = candidate.gfx_queue; candidate.tfer_index = candidate.gfx_index; } vk.physical_device = physical_device; vk.queues = candidate; success = true; } } return success; } bool CheckDeviceFeatures(VkPhysicalDevice device) { VkPhysicalDeviceFeatures2 features2 = { sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 }; VkPhysicalDeviceVulkan12Features features_12 = { sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES }; VkPhysicalDeviceVulkan13Features features_13 = { sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES }; features2.pNext = &features_12; vkGetPhysicalDeviceFeatures2(device, &features2); features2.pNext = &features_13; vkGetPhysicalDeviceFeatures2(device, &features2); VkPhysicalDeviceFeatures features = features2.features; bool result = true; result &= cast(bool)features.shaderUniformBufferArrayDynamicIndexing; result &= cast(bool)features.shaderSampledImageArrayDynamicIndexing; result &= cast(bool)features.shaderStorageBufferArrayDynamicIndexing; result &= cast(bool)features.shaderStorageImageArrayDynamicIndexing; result &= cast(bool)features.samplerAnisotropy; result &= cast(bool)features_12.descriptorIndexing; result &= cast(bool)features_12.bufferDeviceAddress; result &= cast(bool)features_12.descriptorBindingUniformBufferUpdateAfterBind; result &= cast(bool)features_12.descriptorBindingSampledImageUpdateAfterBind; result &= cast(bool)features_12.descriptorBindingStorageImageUpdateAfterBind; result &= cast(bool)features_12.descriptorBindingStorageBufferUpdateAfterBind; result &= cast(bool)features_12.descriptorBindingPartiallyBound; result &= cast(bool)features_12.runtimeDescriptorArray; result &= cast(bool)features_12.shaderSampledImageArrayNonUniformIndexing; result &= cast(bool)features_12.shaderUniformBufferArrayNonUniformIndexing; result &= cast(bool)features_12.timelineSemaphore; result &= cast(bool)features_13.synchronization2; result &= cast(bool)features_13.dynamicRendering; return result; } bool CheckDeviceProperties(a.Arena *arena, VkPhysicalDevice device, VkSurfaceKHR surface, b32* discrete) { bool success = false; VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(device, &props); if (VK_API_VERSION_MINOR(props.apiVersion) >= 3) { u32 ext_count; vkEnumerateDeviceExtensionProperties(device, null, &ext_count, null); VkExtensionProperties[] ext_props = a.AllocArray!(VkExtensionProperties)(arena, ext_count); vkEnumerateDeviceExtensionProperties(device, null, &ext_count, ext_props.ptr); i32 matched = 0; foreach(prop; ext_props) { foreach(ext; VK_DEVICE_EXTENSIONS) { if (strcmp(cast(char*)prop.extensionName, ext) == 0) { matched += 1; break; } } } if (matched == VK_DEVICE_EXTENSIONS.length) { u32 fmt_count, present_count; vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &fmt_count, null); vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &present_count, null); *discrete = props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; success = fmt_count && present_count; } } return success; } QueueInfo CheckQueueProperties(a.Arena *arena, VkPhysicalDevice device, VkSurfaceKHR surface) { const u32 T_BIT = VK_QUEUE_TRANSFER_BIT; const u32 C_BIT = VK_QUEUE_COMPUTE_BIT; const u32 G_BIT = VK_QUEUE_GRAPHICS_BIT; const u32 S_BIT = VK_QUEUE_SPARSE_BINDING_BIT; QueueInfo current = { gfx_index: -1, tfer_index: -1, single_queue: false, }; u32 count; vkGetPhysicalDeviceQueueFamilyProperties(device, &count, null); VkQueueFamilyProperties[] properties = a.AllocArray!(VkQueueFamilyProperties)(arena, count); vkGetPhysicalDeviceQueueFamilyProperties(device, &count, properties.ptr); if (count == 1 && properties[0].queueCount == 1 && u.BitEq(properties[0].queueFlags, T_BIT | C_BIT | G_BIT)) { current.gfx_index = current.tfer_index = 0; current.single_queue = true; } else { bool sparse = false, tfer_only = false; foreach(i, prop; properties) { b32 surface_support; vkGetPhysicalDeviceSurfaceSupportKHR(device, cast(u32)i, surface, &surface_support); if (current.gfx_index < 0 && surface_support && u.BitEq(prop.queueFlags, G_BIT)) { current.gfx_index = cast(i32)i; continue; } if (u.BitEq(prop.queueFlags, T_BIT | S_BIT) && !u.BitEq(prop.queueFlags, G_BIT | C_BIT)) { sparse = true; tfer_only = true; current.tfer_index = cast(i32)i; continue; } if (!(sparse && tfer_only) && u.BitEq(prop.queueFlags, T_BIT | S_BIT)) { sparse = true; current.tfer_index = cast(i32)i; continue; } if (!sparse && !u.BitEq(prop.queueFlags, T_BIT) && u.BitEq(prop.queueFlags, C_BIT)) { tfer_only = true; current.tfer_index = cast(i32)i; continue; } if (!sparse && !tfer_only && u.BitEq(prop.queueFlags, C_BIT)) { current.tfer_index = cast(i32)i; } } if (current.tfer_index < 0) { current.tfer_index = current.gfx_index; } } return current; } pragma(inline): void Push(Vulkan* vk, StepInitialized step) { u.Node!(SI)* node = a.Alloc!(u.Node!(SI)); node.value = step; u.PushFront(&vk.cleanup_list, node, null); } void DestroyRenderer(Vulkan* vk) { foreach(i, arena; vk.frame_arenas) { a.Free(vk.frame_arenas.ptr + i); } a.Free(&vk.arena); } void Destroy(VkInstance instance) { if (instance) { vkDestroyInstance(instance, null); } } void Destroy(VkDebugUtilsMessengerEXT dbg, VkInstance instance) { if (dbg) { vkDestroyDebugUtilsMessengerEXT(instance, dbg, null); } } void Destroy(VkSurfaceKHR surface, VkInstance instance) { if (surface) { vkDestroySurfaceKHR(instance, surface, null); } } void Destroy(VkDevice device) { if (device) { vkDestroyDevice(device, null); } } void Destroy(VmaAllocator vma) { if (vma) { vmaDestroyAllocator(vma); } } void Destroy(VkSwapchainKHR swapchain, ImageView[] views, VkDevice device) { foreach(view; views) { if (view.view) { vkDestroyImageView(device, view.view, null); } } if (swapchain) { vkDestroySwapchainKHR(device, swapchain, null); } } void Destroy(ImageView* view, VkDevice device, VmaAllocator vma) { if (view.view) { vkDestroyImageView(device, view.view, null); } if (view.base.image) { vmaDestroyImage(vma, view.base.image, view.base.alloc); } } void Destroy(VkDescriptorPool pool, VkDescriptorSetLayout[] layouts, VkPipelineLayout pipeline_layout, VkSampler sampler, VkDevice device) { if (sampler) { vkDestroySampler(device, sampler, null); } if (pipeline_layout) { vkDestroyPipelineLayout(device, pipeline_layout, null); } foreach(layout; layouts) { if (layout) { vkDestroyDescriptorSetLayout(device, layout, null); } } if (pool) { vkDestroyDescriptorPool(device, pool, null); } } void Destroy(ImageView* draw, ImageView* depth, VkDevice device, VmaAllocator vma) { Destroy(draw, device, vma); Destroy(depth, device, vma); } void DestroyFS(Vulkan* vk) { if (vk.imm_fence) { vkDestroyFence(vk.device, vk.imm_fence, null); } if (vk.imm_cmd) { vkFreeCommandBuffers(vk.device, vk.imm_pool, 1, &vk.imm_cmd); } if (vk.imm_pool) { vkDestroyCommandPool(vk.device, vk.imm_pool, null); } foreach(i, sem; vk.swapchain_sems) { if (sem) { vkDestroySemaphore(vk.device, sem, null); } } foreach(i; 0 .. FRAME_OVERLAP) { if (vk.render_sems[i]) { vkDestroySemaphore(vk.device, vk.render_sems[i], null); } if (vk.render_fences[i]) { vkDestroyFence(vk.device, vk.render_fences[i], null); } if (vk.cmd_pools[i]) { vkFreeCommandBuffers(vk.device, vk.cmd_pools[i], 1, &vk.cmds[i]); } if (vk.cmd_pools[i]) { vkDestroyCommandPool(vk.device, vk.cmd_pools[i], null); } } } bool InitInstance(Vulkan* vk) { Push(vk, SI.Instance); bool success = true; a.Arena* arena = &vk.frame_arenas[0]; u32 count; vkEnumerateInstanceLayerProperties(&count, null); VkLayerProperties[] layers = a.AllocArray!(VkLayerProperties)(arena, count); vkEnumerateInstanceLayerProperties(&count, layers.ptr); foreach(i, layer; layers) { if (strcmp(cast(char*)&layer.layerName, "VK_LAYER_KHRONOS_validation") == 0) { g_VLAYER_SUPPORT = true; break; } } const char*[] instance_layers = g_VLAYER_SUPPORT && BUILD_DEBUG ? VK_INSTANCE_LAYERS_DEBUG : VK_INSTANCE_LAYERS; const char*[] instance_ext = g_VLAYER_SUPPORT && BUILD_DEBUG ? VK_INSTANCE_EXT_DEBUG : VK_INSTANCE_EXT; VkApplicationInfo app_info = { sType: VK_STRUCTURE_TYPE_APPLICATION_INFO, pApplicationName: "Video Game", applicationVersion: VK_MAKE_API_VERSION(0, 0, 0, 1), pEngineName: "Gears", engineVersion: VK_MAKE_API_VERSION(0, 0, 0, 1), apiVersion: VK_MAKE_API_VERSION(0, 1, 3, 0), }; VkInstanceCreateInfo instance_info = { sType: VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, pApplicationInfo: &app_info, enabledLayerCount: cast(u32)instance_layers.length, ppEnabledLayerNames: instance_layers.ptr, enabledExtensionCount: cast(u32)instance_ext.length, ppEnabledExtensionNames: instance_ext.ptr, }; VkResult result = vkCreateInstance(&instance_info, null, &vk.instance); success = VkCheck("vkCreateInstance failure", result); return success; } bool InitSurface(Vulkan* vk) { Push(vk, SI.Surface); version(linux) { VkXcbSurfaceCreateInfoKHR surface_info = { sType: VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR, connection: vk.window.conn, window: vk.window.window, }; VkResult result = vkCreateXcbSurfaceKHR(vk.instance, &surface_info, null, &vk.surface); } version(Windows) { VkWin32SurfaceCreateInfoKHR surface_info = { sType: VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, hinstance: vk.window.instance, hwnd: vk.window.handle, }; VkResult result = vkCreateWin32SurfaceKHR(vk.instance, &surface_info, null, &vk.surface); } bool success = VkCheck("InitSurface failure", result); return success; } void EnableVLayers(Vulkan* vk) { debug { Push(vk, SI.Debug); if (g_VLAYER_SUPPORT) { VkDebugUtilsMessengerCreateInfoEXT info = { sType: VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, messageSeverity: 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: cast(PFN_vkDebugUtilsMessengerCallbackEXT)&DebugCallback, }; VkResult result = vkCreateDebugUtilsMessengerEXT(vk.instance, &info, null, &vk.dbg_msg); if (result != VK_SUCCESS) { Logf("EnableVLayers failed to initialize, will continue without validation: %s", VkResultStr(result)); } } else { Logf("EnableVLayers warning: Not supported on current device, continuing without"); } } }