import vulkan_funcs; import vulkan_logging; import vulkan_util; import std.stdio; import std.algorithm.comparison; import core.stdc.string : strcmp, memcpy; import core.stdc.stdio : Printf = printf; import std.format : sformat; import std.math.rounding : Ceil = ceil; version(VULKAN_DEBUG) { const BUILD_DEBUG = true; } else { const BUILD_DEBUG = false; } alias InitRenderer = Init; alias Renderer = Vulkan; alias Shader = VkShaderModule; alias Pipeline = u32; alias Attribute = VkVertexInputAttributeDescription; alias SpecEntry = VkSpecializationMapEntry; alias RenderPass = VkRenderPass; alias DescSetLayout = VkDescriptorSetLayout; alias PipelineLayout = VkPipelineLayout; alias DescLayoutBinding = VkDescriptorSetLayoutBinding; alias DescWrite = VkWriteDescriptorSet; struct DescSet { VkDescriptorSet handle; u32 dynamic_count; } bool g_VLAYER_SUPPORT = false; version(VK_DEBUG_PRINTF) { bool g_DEBUG_PRINTF = true; } else { bool g_DEBUG_PRINTF = false; } const FRAME_OVERLAP = 2; const MAX_SETS = 1000; const DESC_ARRAY_SIZE = 256; version(linux) { const string[] VULKAN_LIBS = [ "libvulkan.so.1", "libvulkan.so" ]; struct PlatformHandles { xcb_connection_t *conn; xcb_window_t window; } } version(Windows) { const string[] VULKAN_LIBS = [ "vulkan-1.dll" ]; struct PlatformHandles { HINSTANCE hinstance; HWND hwnd; } } 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_BASE_DEVICE_EXTENSIONS = [ cast(char*)VK_KHR_SWAPCHAIN_EXTENSION_NAME, cast(char*)VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME, cast(char*)VK_KHR_8BIT_STORAGE_EXTENSION_NAME, cast(char*)VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, ]; const char*[] VK_AMD_DEVICE_EXTENSIONS = [ cast(char*)VK_AMD_SHADER_INFO_EXTENSION_NAME, ]; version(AMD_GPU) { version(VULKAN_DEBUG) const char*[] VK_DEVICE_EXTENSIONS = VK_AMD_DEVICE_EXTENSIONS ~ VK_BASE_DEVICE_EXTENSIONS; } else { const char*[] VK_DEVICE_EXTENSIONS = VK_BASE_DEVICE_EXTENSIONS; } const VkFormat[] VK_IMAGE_FORMATS = [ VK_FORMAT_R16G16B16A16_UNORM, VK_FORMAT_R8G8B8A8_UNORM, ]; enum ShaderStage : VkShaderStageFlagBits { None = cast(VkShaderStageFlagBits)0, Vertex = VK_SHADER_STAGE_VERTEX_BIT, Fragment = VK_SHADER_STAGE_FRAGMENT_BIT, Compute = VK_SHADER_STAGE_COMPUTE_BIT, All = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT, } alias SS = ShaderStage; enum InputRate : int { Vertex = VK_VERTEX_INPUT_RATE_VERTEX, Instance = VK_VERTEX_INPUT_RATE_INSTANCE, } alias IR = InputRate; enum ImageUsage : VkImageUsageFlagBits { None = cast(VkImageUsageFlagBits)0, Draw = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, Depth = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, Texture = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, Convert = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, Storage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, Swapchain = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, } alias IU = ImageUsage; enum FrontFace: VkFrontFace { CCW = VK_FRONT_FACE_COUNTER_CLOCKWISE, CW = VK_FRONT_FACE_CLOCKWISE, } alias FF = FrontFace; enum Format : VkFormat { UINT = VK_FORMAT_R32_UINT, R_F32 = VK_FORMAT_R32_SFLOAT, RG_F32 = VK_FORMAT_R32G32_SFLOAT, RGB_F32 = VK_FORMAT_R32G32B32_SFLOAT, RGBA_F32 = VK_FORMAT_R32G32B32A32_SFLOAT, RGBA_UINT = VK_FORMAT_B8G8R8A8_UINT, R_U32 = VK_FORMAT_R32_UINT, RG_U32 = VK_FORMAT_R32G32_UINT, RGBA_UNORM = VK_FORMAT_R8G8B8A8_UNORM, RGBA_SRGB = VK_FORMAT_R8G8B8A8_SRGB, D_SF32 = VK_FORMAT_D32_SFLOAT, } alias FMT = Format; enum BufferType : VkBufferUsageFlagBits { None = cast(VkBufferUsageFlagBits)0, Vertex = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, Index = VK_BUFFER_USAGE_INDEX_BUFFER_BIT, Uniform = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, Storage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, Staging = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, BufferView = VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT, } alias BT = BufferType; enum ImageLayout : VkImageLayout { Undefined = VK_IMAGE_LAYOUT_UNDEFINED, General = VK_IMAGE_LAYOUT_GENERAL, ColorAttach = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, ReadOnly = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, TransferDst = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, TransferSrc = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, } alias IL = ImageLayout; struct Image { VkImage image; VmaAllocation alloc; Format format; VkImageLayout layout; u32 w; u32 h; bool depth_image; ImageUsage usage; } struct BufferView { Buffer base; VkBufferView view; alias base this; } struct ImageView { Image base; VkImageView view; ImageLayout shader_layout; alias base this; } struct Buffer { VkBuffer buffer; VmaAllocation alloc; u64 size; } struct Descriptor { DescType type; u32 binding; union { Buffer buf; Image image; ImageView view; BufferView buf_view; VkSampler sampler; } } enum MipmapMode : VkSamplerMipmapMode { Nearest = VK_SAMPLER_MIPMAP_MODE_NEAREST, Linear = VK_SAMPLER_MIPMAP_MODE_LINEAR, } enum PipelineType : int { Graphics, Compute, } alias PT = PipelineType; enum BlendFactor : VkBlendFactor { Zero = VK_BLEND_FACTOR_ZERO, One = VK_BLEND_FACTOR_ONE, SrcColor = VK_BLEND_FACTOR_SRC_COLOR, OneMinusSrcColor = VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, DstColor = VK_BLEND_FACTOR_DST_COLOR, OneMinusDstColor = VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, SrcAlpha = VK_BLEND_FACTOR_SRC_ALPHA, OneMinusSrcAlpha = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, DstAlpha = VK_BLEND_FACTOR_DST_ALPHA, OneMinusDstAlpha = VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA, ConstColor = VK_BLEND_FACTOR_CONSTANT_COLOR, OneMinusConstColor = VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR, ConstAlpha = VK_BLEND_FACTOR_CONSTANT_ALPHA, OneMinusConstAlpha = VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA, SrcAlphaSaturate = VK_BLEND_FACTOR_SRC_ALPHA_SATURATE, Src1Color = VK_BLEND_FACTOR_SRC1_COLOR, OneMinusSrc1Color = VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR, Src1Alpha = VK_BLEND_FACTOR_SRC1_ALPHA, OneMinusSrc1Alpha = VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, } alias BF = BlendFactor; enum BlendOp : VkBlendOp { Add = VK_BLEND_OP_ADD, Sub = VK_BLEND_OP_SUBTRACT, ReverseSub = VK_BLEND_OP_REVERSE_SUBTRACT, Min = VK_BLEND_OP_MIN, Max = VK_BLEND_OP_MAX, } alias BO = BlendOp; struct Specialization { u64 size; SpecEntry[] entries; void* data; } struct GfxPipelineInfo { u8[] vertex_shader; u8[] frag_shader; InputRate input_rate; u32 input_rate_stride; Attribute[] vertex_attributes; Specialization vert_spec; Specialization frag_spec; bool self_dependency; PipelineLayout layout; FrontFace front_face; VkBlendFactor src_color; VkBlendFactor dst_color; VkBlendOp color_op; VkBlendFactor src_alpha; VkBlendFactor dst_alpha; VkBlendOp alpha_op; } struct CompPipelineInfo { u8[] shader; Specialization spec; PipelineLayout layout; } enum StepInitialized : u32 { Renderer = 1, Instance, Debug, Surface, Device, Vma, Buffers, FrameStructures, Swapchain, DrawImages, DescriptorPools, Pipelines, } alias SI = StepInitialized; enum DescType : VkDescriptorType { // VkSampler Sampler = VK_DESCRIPTOR_TYPE_SAMPLER, // ImageView CombinedSampler = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, Image = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, StorageImage = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, InputAttach = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, // BufferView UniformTexelBuf = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, StorageTexelBuf = VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, // Buffer Uniform = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, DynamicUniform = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, Storage = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DynamicStorage = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, Max, } alias DT = DescType; struct MappedBuffer(T) { Buffer base; T[] data; u64 offset; alias base this; } struct Vulkan { Arena arena; Arena[FRAME_OVERLAP] frame_arenas; u32 frame_index; u32 semaphore_index; PlatformHandles platform_handles; u32 window_h, window_w; 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; VkRenderPass render_pass; VkFramebuffer framebuffer; ImageView[] present_images; u32 image_index; Descriptor draw_image; Descriptor depth_image; VkCommandPool[FRAME_OVERLAP] cmd_pools; VkCommandBuffer[FRAME_OVERLAP] cmds; VkCommandPool comp_cmd_pool; VkCommandBuffer comp_cmd; VkFence comp_fence; VkSemaphore[] submit_sems; VkSemaphore[FRAME_OVERLAP] acquire_sems; VkFence[FRAME_OVERLAP] render_fences; VkCommandPool imm_pool; VkCommandBuffer imm_cmd; VkFence imm_fence; VkDescriptorPool active_pool; SLList!(VkDescriptorPool) full_pools; PipelineHandles[] pipeline_handles; u32 pipeline_count; VkPipeline[FRAME_OVERLAP] last_pipeline; DescSetLayout global_set_layout; DescSet global_set; MappedBuffer!(u8) transfer_buf; QueueInfo queues; Pipeline r_to_rgba_pipeline; Pipeline rg_to_rgba_pipeline; Pipeline rgb_to_rgba_pipeline; DescSet conv_desc_set; VkDescriptorSetLayout conv_desc_layout; VkPipelineLayout conv_pipeline_layout; VkPipeline last_comp_pipeline; bool compute_pass_started; f32[4] color_clear; f32[4] depth_clear; alias queues this; } struct ConvPushConst { u32 x; u32 y; } struct PipelineHandles { VkPipeline handle; VkPipelineBindPoint type; VkPipelineLayout layout; Pipeline index; } struct QueueInfo { i32 gfx_index, tfer_index; VkQueue gfx_queue, tfer_queue; bool single_queue; } u8[] CONVERT_SHADER_BYTES = cast(u8[])import("convert.comp.spv"); DescSetLayout CreateDescSetLayout(Vulkan* vk, DescLayoutBinding[] bindings) { VkDescriptorSetLayoutCreateInfo layout_info = { sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, bindingCount: cast(u32)bindings.length, pBindings: bindings.ptr, }; DescSetLayout layout; VkResult result = vkCreateDescriptorSetLayout(vk.device, &layout_info, null, &layout); VkCheckA("vkCreateDescriptorSetLayout failure", result); return layout; } DescSet AllocDescSet(Vulkan* vk, DescSetLayout layout, u32 dynamic_count = 0) { VkDescriptorSetAllocateInfo alloc_info = { sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, descriptorSetCount: 1, pSetLayouts: &layout, descriptorPool: vk.active_pool, }; DescSet set = { dynamic_count: dynamic_count, }; VkResult result = vkAllocateDescriptorSets(vk.device, &alloc_info, &set.handle); if(result == VK_ERROR_OUT_OF_POOL_MEMORY || result == VK_ERROR_FRAGMENTED_POOL) { PushDescriptorPool(vk); alloc_info.descriptorPool = vk.active_pool; result = vkAllocateDescriptorSets(vk.device, &alloc_info, &set.handle); VkCheckA("vkAllocateDescriptorSets failure", result); } return set; } PipelineLayout CreatePipelineLayout(T)(Vulkan* vk, T layouts, u32 push_const_size, bool compute = false) if(is(T: DescSetLayout) || is(T: DescSetLayout[])) { VkShaderStageFlagBits stage = (compute ? VK_SHADER_STAGE_COMPUTE_BIT : VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); DescSetLayout[] desc_layouts; static if(is(T: DescSetLayout)) { desc_layouts = Alloc!(DescSetLayout)(&vk.frame_arenas[vk.frame_index], 2); desc_layouts[0] = vk.global_set_layout; desc_layouts[1] = layouts; } else static if(is(T: DescSetLayout[])) { desc_layouts = Alloc!(DescSetLayout)(&vk.frame_arenas[vk.frame_index], layouts.length + 1); desc_layouts[0] = vk.global_set_layout; foreach(i; 0 .. layouts.length) { desc_layouts[i+1] = layouts[i]; } } VkPushConstantRange const_range = { offset: 0, size: cast(VkDeviceSize)push_const_size, stageFlags: stage, }; VkPipelineLayoutCreateInfo layout_info = { sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, setLayoutCount: cast(u32)desc_layouts.length, pSetLayouts: desc_layouts.ptr, pushConstantRangeCount: (push_const_size > 0 ? 1 : 0), pPushConstantRanges: (push_const_size > 0 ? &const_range : null), }; PipelineLayout layout; VkResult result = vkCreatePipelineLayout(vk.device, &layout_info, null, &layout); VkCheckA("CreatePipelineLayout failure", result); return layout; } void CreateBuffer(Vulkan* vk, Descriptor* desc, BufferType type, u64 size, bool host_visible, DescType desc_type) { desc.type = desc_type; CreateBuffer(vk, &desc.buf, type, size, host_visible); } void CreateBuffer(Vulkan* vk, Buffer* buf, BufferType type, u64 size, bool host_visible) { assert(type != BT.None, "CreateBuffer failure: type is None"); VkBufferCreateInfo buffer_info = { sType: VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, usage: type, size: size, }; VmaAllocationCreateInfo alloc_info = { usage: VMA_MEMORY_USAGE_UNKNOWN, flags: VMA_ALLOCATION_CREATE_MAPPED_BIT, }; if(host_visible) { alloc_info.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; alloc_info.preferredFlags = VK_MEMORY_PROPERTY_HOST_CACHED_BIT; } else { buffer_info.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; alloc_info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; } u32[2] indices = [vk.queues.gfx_index, vk.queues.tfer_index]; if(vk.queues.gfx_index != vk.queues.tfer_index) { buffer_info.sharingMode = VK_SHARING_MODE_CONCURRENT; buffer_info.queueFamilyIndexCount = 2; buffer_info.pQueueFamilyIndices = indices.ptr; } VmaAllocationInfo vma_info; VkResult result = vmaCreateBuffer(vk.vma, &buffer_info, &alloc_info, &buf.buffer, &buf.alloc, &vma_info); // TODO: handle errors here then reallocate buffer VkCheck("CreateBuffer failure: vmaCreateBuffer error", result); buf.size = size; } void BindBuffers(Vulkan* vk, Buffer* index_buffer, Buffer* vertex_buffer) { VkDeviceSize offset = 0; vkCmdBindIndexBuffer(vk.cmds[vk.frame_index], index_buffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdBindVertexBuffers(vk.cmds[vk.frame_index], 0, 1, &vertex_buffer.buffer, &offset); } void BindBuffers(T)(Vulkan* vk, MappedBuffer!(u32)* index_buffer, MappedBuffer!(T)* vertex_buffer) { VkDeviceSize offset = 0; vkCmdBindIndexBuffer(vk.cmds[vk.frame_index], index_buffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdBindVertexBuffers(vk.cmds[vk.frame_index], 0, 1, &vertex_buffer.buffer, &offset); } MappedBuffer!(T) CreateMappedBuffer(T)(Vulkan* vk, BufferType type, u64 count) { MappedBuffer!(T) buf; CreateBuffer(vk, &buf.base, type, T.sizeof * count, true); buf.data = MapBuffer!(T)(vk, &buf.base, count); return buf; } T[] MapBuffer(T)(Vulkan* vk, Buffer* buffer, u64 count) { void* ptr; vmaMapMemory(vk.vma, buffer.alloc, &ptr); return (cast(T*)ptr)[0 .. count]; } VkImage CurrentImage(Vulkan* vk) { return vk.present_images[vk.image_index].image; } void BeginFrame(Vulkan* vk) { // TODO: move vkWaitForFences so it no longer holds up the frame, will need to change how fences are handled in regards to images though VkResult result = vkWaitForFences(vk.device, 1, vk.render_fences.ptr + vk.frame_index, VK_TRUE, 1000000000); VkCheckA("BeginFrame failure: vkWaitForFences error", result); result = vkResetFences(vk.device, 1, vk.render_fences.ptr + vk.frame_index); VkCheckA("BeginFrame failure: vkResetFences error", result); result = vkAcquireNextImageKHR(vk.device, vk.swapchain, 1000000000, vk.acquire_sems[vk.frame_index], null, &vk.image_index); if(result == VK_ERROR_OUT_OF_DATE_KHR) { RecreateSwapchain(vk); } else if(result != VK_SUBOPTIMAL_KHR) { VkCheckA("BeginFrame failure: vkAcquireNextImageKHR error", result); } result = vkResetCommandBuffer(vk.cmds[vk.frame_index], 0); VkCheckA("BeginFrame failure: vkResetCommandBuffer failure", result); VkCommandBufferBeginInfo cmd_info = { sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, flags: VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, }; result = vkBeginCommandBuffer(vk.cmds[vk.frame_index], &cmd_info); VkCheckA("BeginFrame failure: vkBeginCommandBuffer error", result); } void SetClearColors(Vulkan* vk, f32[4] color_clear, f32[4] depth_clear) { vk.color_clear = color_clear; vk.depth_clear = depth_clear; } void BeginRendering(Vulkan* vk) { Transition(vk.cmds[vk.frame_index], &vk.draw_image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); Transition(vk.cmds[vk.frame_index], &vk.depth_image, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL); VkImage image = CurrentImage(vk); Transition(vk.cmds[vk.frame_index], image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); VkClearValue[2] clear_color = [ { color: { float32: vk.color_clear } }, { color: { float32: vk.depth_clear } }, ]; VkRenderPassBeginInfo pass_info = { sType: VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, renderPass: vk.render_pass, framebuffer: vk.framebuffer, renderArea: { offset: { x: 0, y: 0 }, extent: { width: vk.swapchain_extent.width, height: vk.swapchain_extent.height, }, }, clearValueCount: cast(u32)clear_color.length, pClearValues: clear_color.ptr, }; vkCmdBeginRenderPass(vk.cmds[vk.frame_index], &pass_info, VK_SUBPASS_CONTENTS_INLINE); } pragma(inline): u32[2] GetExtent(Vulkan* vk) { u32[2] extent; extent[0] = vk.swapchain_extent.width; extent[1] = vk.swapchain_extent.height; return extent; } pragma(inline): f32 GetAspect(Vulkan* vk) { return cast(f32)(vk.swapchain_extent.width) / cast(f32)(vk.swapchain_extent.height); } void PrepAuxImage(Vulkan* vk, ImageView* view) { Transition(vk.cmds[vk.frame_index], view, VK_IMAGE_LAYOUT_GENERAL); } void PrepComputeDrawImage(Vulkan* vk) { Transition(vk.cmds[vk.frame_index], &vk.draw_image, VK_IMAGE_LAYOUT_GENERAL); } void FinishRendering(Vulkan* vk) { vkCmdEndRenderPass(vk.cmds[vk.frame_index]); } void SubmitAndPresent(Vulkan* vk) { scope(exit) { vk.last_pipeline[vk.frame_index] = null; vk.frame_index = (vk.frame_index + 1) % FRAME_OVERLAP; } VkImage image = CurrentImage(vk); VkSemaphore acquire_sem = vk.acquire_sems[vk.frame_index]; VkSemaphore submit_sem = vk.submit_sems[vk.image_index]; Transition(vk.cmds[vk.frame_index], &vk.draw_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); VkExtent2D extent = { width: vk.swapchain_extent.width, height: vk.swapchain_extent.height, }; // TODO: Find out how to copy from same dimension images (pretty sure its not blitting) Copy(vk.cmds[vk.frame_index], &vk.draw_image.view.base, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, extent, extent); Transition(vk.cmds[vk.frame_index], image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); VkResult result = vkEndCommandBuffer(vk.cmds[vk.frame_index]); VkCheckA("FinishFrame failure: vkEndCommandBuffer error", result); VkCommandBufferSubmitInfo cmd_info = { sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, commandBuffer: vk.cmds[vk.frame_index], }; VkSemaphoreSubmitInfo wait_info = { sType: VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, semaphore: acquire_sem, stageMask: VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, value: 1, }; VkSemaphoreSubmitInfo signal_info = { sType: VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, semaphore: submit_sem, stageMask: VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT, value: 1, }; VkSubmitInfo2 submit_info = { sType: VK_STRUCTURE_TYPE_SUBMIT_INFO_2, waitSemaphoreInfoCount: 1, pWaitSemaphoreInfos: &wait_info, signalSemaphoreInfoCount: 1, pSignalSemaphoreInfos: &signal_info, commandBufferInfoCount: 1, pCommandBufferInfos: &cmd_info, }; result = vkQueueSubmit2(vk.queues.gfx_queue, 1, &submit_info, vk.render_fences[vk.frame_index]); VkCheckA("FinishFrame failure: vkQueueSubmit2 error", result); VkPresentInfoKHR present_info = { sType: VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, swapchainCount: 1, pSwapchains: &vk.swapchain, waitSemaphoreCount: 1, pWaitSemaphores: &submit_sem, pImageIndices: &vk.image_index, }; result = vkQueuePresentKHR(vk.queues.gfx_queue, &present_info); if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { RecreateSwapchain(vk); } else { VkCheckA("FinishFrame failure: vkQueuePresentKHR failure", result); } } void Draw(Vulkan* vk, u32 index_count, u32 instance_count) { vkCmdDraw(vk.cmds[vk.frame_index], index_count, instance_count, 0, 0); } void DrawIndexed(Vulkan* vk, u32 index_count, u32 instance_count, u32 index_offset) { vkCmdDrawIndexed(vk.cmds[vk.frame_index], index_count, instance_count, index_offset, 0, 0); } bool ImmSubmitStart(Vulkan* vk) { VkResult result = vkWaitForFences(vk.device, 1, &vk.imm_fence, true, 999999999); bool success = VkCheck("ImmSubmit failure: vkWaitForFences error", result); if(success) { result = vkResetFences(vk.device, 1, &vk.imm_fence); success = VkCheck("ImmSubmit failure: vkResetFences error", result); } if(success) { result = vkResetCommandBuffer(vk.imm_cmd, 0); success = VkCheck("ImmSubmit failure: vkResetCommandBuffer error", result); } if(success) { VkCommandBufferBeginInfo cmd_info = { sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, flags: VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, }; result = vkBeginCommandBuffer(vk.imm_cmd, &cmd_info); success = VkCheck("ImmSubmit failure: vkBeginCommandBuffer error", result); } return success; } bool ImmSubmitFinish(Vulkan* vk) { VkCommandBufferSubmitInfo cmd_info = { sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, commandBuffer: vk.imm_cmd, }; VkSubmitInfo2 submit_info = { sType: VK_STRUCTURE_TYPE_SUBMIT_INFO_2, commandBufferInfoCount: 1, pCommandBufferInfos: &cmd_info, }; VkResult result = vkQueueSubmit2(vk.tfer_queue, 1, &submit_info, vk.imm_fence); return VkCheck("ImmSubmit failure: vkQueueSubmit2 error", result); } bool ImmSubmit(Vulkan* vk, Image* image, VkBufferImageCopy copy, void function(Vulkan*, Image*, VkBufferImageCopy) fn) { bool success = ImmSubmitStart(vk); if(success) { fn(vk, image, copy); VkResult result = vkEndCommandBuffer(vk.imm_cmd); success = VkCheck("ImmSubmit failure: vkEndCommandBuffer error", result); } if(success) { success = ImmSubmitFinish(vk); } return success; } bool ImmSubmit(Vulkan* vk, Buffer* buf, VkBufferCopy copy, void function(Vulkan*, Buffer*, VkBufferCopy) fn) { bool success = ImmSubmitStart(vk); if(success) { fn(vk, buf, copy); VkResult result = vkEndCommandBuffer(vk.imm_cmd); success = VkCheck("ImmSubmit failure: vkEndCommandBuffer error", result); } if(success) { success = ImmSubmitFinish(vk); } return success; } bool TransferAssets(Vulkan* vk) { return true; } void WaitForTransfers(Vulkan* vk) { vkWaitForFences(vk.device, 1, &vk.imm_fence, VK_TRUE, u64.max); } void CreateImageView(Vulkan* vk, Descriptor* view, u32 w, u32 h, u32 ch, u8[] data, DescType type = DT.Image) { ImageDescCheck(type); view.type = type; CreateImageView(vk, &view.view, w, h, ch, data); } pragma(inline): void CreateImageView(Vulkan* vk, ImageView* view, u32 w, u32 h, u32 ch, u8[] data) { CreateImageView(vk, view, w, h, FMT.RGBA_UNORM, IU.Texture); if(ch == 4) { bool result = Transfer(vk, view, data, w, h); assert(result); } else { Descriptor buf = { binding: 1 }; CreateBuffer(vk, &buf, BT.Storage, w * h * ch, false, DT.Storage); bool result = Transfer(vk, &buf.buf, data); assert(result, "CreateImageView failure: Buffer Transfer error"); Descriptor conv_view = { binding: 0 }; CreateImageView(vk, &conv_view, w, h, FMT.RGBA_F32, IU.Convert, DT.StorageImage); Write(vk, vk.conv_desc_set, [conv_view, buf]); BeginComputePass(vk); Pipeline pipeline = ch == 1 ? vk.r_to_rgba_pipeline : ch == 2 ? vk.rg_to_rgba_pipeline : vk.rgb_to_rgba_pipeline; Bind(vk, pipeline, vk.conv_desc_set, true); ConvPushConst pc = { x: w, y: h }; vkCmdPushConstants( vk.comp_cmd, vk.conv_pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, ConvPushConst.sizeof, &pc ); Transition(vk.comp_cmd, &conv_view, VK_IMAGE_LAYOUT_GENERAL); Dispatch(vk, vk.comp_cmd); Transition(vk.comp_cmd, view, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); VkExtent2D extent = { width: w, height: h }; Copy(vk.comp_cmd, &conv_view.view.base, &view.base, extent, extent); Transition(vk.comp_cmd, view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); FinishComputePass(vk); vkWaitForFences(vk.device, 1, &vk.comp_fence, VK_TRUE, u64.max); vkQueueWaitIdle(vk.tfer_queue); Destroy(vk, &buf); Destroy(vk, &conv_view); } } void CreateImageView(Vulkan* vk, Descriptor* desc, u32 w, u32 h, Format format, ImageUsage usage, DescType type) { ImageDescCheck(type); desc.type = type; CreateImageView(vk, &desc.view, w, h, format, usage); } pragma(inline): void CreateImageView(Vulkan* vk, ImageView* view, u32 w, u32 h, Format format, ImageUsage usage) { 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, format: format, tiling: VK_IMAGE_TILING_OPTIMAL, initialLayout: VK_IMAGE_LAYOUT_UNDEFINED, usage: usage, samples: VK_SAMPLE_COUNT_1_BIT, extent: { width: w, height: h, depth: 1, }, }; u32[2] indices = [vk.gfx_index, vk.tfer_index]; if(vk.gfx_index != vk.tfer_index) { image_info.sharingMode = VK_SHARING_MODE_CONCURRENT; image_info.queueFamilyIndexCount = 2; image_info.pQueueFamilyIndices = indices.ptr; } VkResult result = vmaCreateImage(vk.vma, &image_info, &alloc_info, &view.image, &view.alloc, null); // TODO: handle errors and realloc VkCheck("CreateImageView failure: vmaCreateImage error", result); VkImageViewCreateInfo view_info = { sType: VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, image: view.image, viewType: VK_IMAGE_VIEW_TYPE_2D, format: format, subresourceRange: { aspectMask: (usage == IU.Depth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT), levelCount: 1, layerCount: 1, }, }; result = vkCreateImageView(vk.device, &view_info, null, &view.view); // TODO: also handle here VkCheck("CreateImageView failure: vkCreateImageView error", result); view.layout = VK_IMAGE_LAYOUT_UNDEFINED; view.format = format; view.w = w; view.h = h; view.depth_image = usage == IU.Depth; view.usage = usage; if(usage == IU.Draw || usage == IU.Depth || usage == IU.Convert) { view.shader_layout = IL.General; } else if(usage == IU.Texture) { view.shader_layout = IL.ReadOnly; } else assert(false, "Unimplemented usage"); } pragma(inline): void BeginComputePass(Vulkan* vk) { VkResult result = vkWaitForFences(vk.device, 1, &vk.comp_fence, VK_TRUE, 1000000000); VkCheckA("BeginComputePass failure: vkWaitForFences error", result); result = vkResetFences(vk.device, 1, &vk.comp_fence); VkCheckA("BeginComputepass failure: vkResetFences error", result); result = vkResetCommandBuffer(vk.comp_cmd, 0); VkCheckA("BeginComputePass failure: vkResetCommandBuffer error", result); VkCommandBufferBeginInfo cmd_info = { sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, flags: VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, }; result = vkBeginCommandBuffer(vk.comp_cmd, &cmd_info); VkCheckA("BeginComputePass failure: vkBeginCommandBuffer error", result); } pragma(inline): void FinishComputePass(Vulkan* vk) { VkResult result = vkEndCommandBuffer(vk.comp_cmd); VkCheckA("FinishComputePass failure: vkEndCommandBuffer error", result); VkCommandBufferSubmitInfo cmd_info = { sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, commandBuffer: vk.comp_cmd, }; VkSubmitInfo2 submit_info = { sType: VK_STRUCTURE_TYPE_SUBMIT_INFO_2, commandBufferInfoCount: 1, pCommandBufferInfos: &cmd_info, }; result = vkQueueSubmit2(vk.gfx_queue, 1, &submit_info, vk.comp_fence); VkCheckA("FinishComputePass failure: vkQueueSubmit2 error", result); } pragma(inline): void CreateBufferView(Vulkan* vk, Descriptor* desc, u64 size, Format format, DescType type) { CreateBuffer(vk, &desc.buf_view.base, BT.BufferView, size, false); VkBufferViewCreateInfo info = { sType: VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO, buffer: desc.buf_view.buffer, format: format, range: size, }; VkResult result = vkCreateBufferView(vk.device, &info, null, &desc.buf_view.view); VkCheckA("CreateBufferView failure: vkCreateBufferView failed", result); desc.buf_view.size = size; } void PushConstants(T)(Vulkan* vk, Pipeline pipeline_id, T* pc) { assert(pipeline_id > 0, "PushConstants pipeline_id == 0"); PipelineHandles* pipeline = vk.pipeline_handles.ptr + pipeline_id; VkShaderStageFlags stage = (pipeline.type == VK_PIPELINE_BIND_POINT_GRAPHICS ? VK_SHADER_STAGE_VERTEX_BIT|VK_SHADER_STAGE_FRAGMENT_BIT : VK_SHADER_STAGE_COMPUTE_BIT); vkCmdPushConstants( vk.cmds[vk.frame_index], pipeline.layout, stage, 0, T.sizeof, pc ); } void ImageBarrier(Vulkan* vk) { VkMemoryBarrier2 barrier = { sType: VK_STRUCTURE_TYPE_MEMORY_BARRIER_2, srcStageMask: VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, srcAccessMask: VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, dstStageMask: VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, dstAccessMask: VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, }; VkDependencyInfo dependency = { sType: VK_STRUCTURE_TYPE_DEPENDENCY_INFO, dependencyFlags: VK_DEPENDENCY_BY_REGION_BIT, memoryBarrierCount: 1, pMemoryBarriers: &barrier, }; vkCmdPipelineBarrier2(vk.cmds[vk.frame_index], &dependency); } bool Transfer(T)(Vulkan* vk, Buffer* buf, T[] data) { u8[] u8_data = (cast(u8*)(data.ptr))[0 .. T.sizeof * data.length]; return Transfer(vk, buf, u8_data); } bool Transfer(Vulkan* vk, Buffer* buf, u8[] data) { bool success = TransferReady(vk); u64 copied = 0; while(copied != data.length && success) { if(copied != 0) { success = TransferReady(vk); if(!success) { break; } } u64 transfer_length = cast(u64)vk.transfer_buf.data.length; u64 data_length = cast(u64)data.length - copied; u64 copy_length = transfer_length > data_length ? data_length : transfer_length; vk.transfer_buf.data[0 .. copy_length] = data[copied .. copy_length]; auto fn = function(Vulkan* vk, Buffer* buf, VkBufferCopy copy) { vkCmdCopyBuffer(vk.imm_cmd, vk.transfer_buf.buffer, buf.buffer, 1, ©); }; VkBufferCopy copy = { srcOffset: 0, dstOffset: copied, size: copy_length, }; success = ImmSubmit(vk, buf, copy, fn); copied += copy_length; } WaitForTransfers(vk); return success; } bool Transfer(T)(Vulkan* vk, Buffer* buf, T* ptr) { assert(T.sizeof < vk.transfer_buf.data.length, "Transfer failure: structure size is too large"); bool success = TransferReady(vk); if(success) { memcpy(vk.transfer_buf.data.ptr, ptr, T.sizeof); auto fn = function(Vulkan* vk, Buffer* buf, VkBufferCopy copy) { vkCmdCopyBuffer(vk.imm_cmd, vk.transfer_buf.buffer, buf.buffer, 1, ©); }; VkBufferCopy copy = { srcOffset: 0, dstOffset: 0, size: T.sizeof, }; success = ImmSubmit(vk, buf, copy, fn); } WaitForTransfers(vk); return success; } // Needs to be done before writing to the transfer buffer as otherwise it'll overwrite it while previous transfers are occurring bool TransferReady(Vulkan* vk) { VkResult result = vkWaitForFences(vk.device, 1, &vk.imm_fence, true, 999999999); return VkCheck("Transfer failure: vkWaitForFences error", result); } pragma(inline): bool Transfer(Vulkan* vk, ImageView* view, u8[] data, u32 w, u32 h) { return Transfer(vk, &view.base, data, w, h); } bool Transfer(Vulkan* vk, Image* image, u8[] data, u32 w, u32 h) { bool success = true; u64 copied = 0; while(copied != data.length) { u64 transfer_length = cast(u64)vk.transfer_buf.data.length; u64 data_length = cast(u64)data.length - copied; u64 copy_length = transfer_length > data_length ? data_length : transfer_length; vk.transfer_buf.data[0 .. copy_length] = data[copied .. copy_length]; auto fn = function(Vulkan* vk, Image* image, VkBufferImageCopy copy) { Transition(vk.imm_cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); vkCmdCopyBufferToImage(vk.imm_cmd, vk.transfer_buf.buffer, image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); Transition(vk.imm_cmd, image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); }; VkBufferImageCopy copy = { bufferRowLength: w, bufferImageHeight: h, imageSubresource: { aspectMask: VK_IMAGE_ASPECT_COLOR_BIT, layerCount: 1, }, imageExtent: { width: w, height: h, depth: 1, }, bufferOffset: copied, }; success = ImmSubmit(vk, image, copy, fn); copied += copy_length; } WaitForTransfers(vk); return success; } pragma(inline): void Copy(VkCommandBuffer cmd, Image* src, Image* dst, VkExtent2D src_ext, VkExtent2D dst_ext) { Copy(cmd, src.image, dst.image, src.layout, dst.layout, src_ext, dst_ext); } pragma(inline): void Copy(VkCommandBuffer cmd, Image* src, VkImage dst, VkImageLayout dst_layout, VkExtent2D src_ext, VkExtent2D dst_ext) { Copy(cmd, src.image, dst, src.layout, dst_layout, src_ext, dst_ext); } pragma(inline): void Copy(VkCommandBuffer cmd, VkImage src, VkImage dst, VkImageLayout src_layout, VkImageLayout dst_layout, VkExtent2D src_ext, VkExtent2D dst_ext) { VkImageBlit blit = { srcOffsets: [ { x: 0, y: 0 }, { x: cast(i32)src_ext.width, y: cast(i32)src_ext.height, z: 1 }, ], dstOffsets: [ { x: 0, y: 0 }, { x: cast(i32)dst_ext.width, y: cast(i32)dst_ext.height, z: 1 }, ], srcSubresource: { aspectMask: VK_IMAGE_ASPECT_COLOR_BIT, baseArrayLayer: 0, layerCount: 1, mipLevel: 0, }, dstSubresource: { aspectMask: VK_IMAGE_ASPECT_COLOR_BIT, baseArrayLayer: 0, layerCount: 1, mipLevel: 0, }, }; vkCmdBlitImage( cmd, src, src_layout, dst, dst_layout, 1, &blit, VK_FILTER_LINEAR ); } void Bind(Vulkan* vk, Pipeline pipeline_handle, DescSet[] sets, bool compute = false) { assert(pipeline_handle > 0, "Bind failure: pipeline is 0"); VkCommandBuffer cmd = (compute ? vk.comp_cmd : vk.cmds[vk.frame_index]); PipelineHandles* pipeline = vk.pipeline_handles.ptr + pipeline_handle; BindPipeline(vk, cmd, pipeline); u32[] offsets; if(vk.global_set.dynamic_count > 0) { offsets = Alloc!(u32)(&vk.frame_arenas[vk.frame_index], vk.global_set.dynamic_count); } vkCmdBindDescriptorSets( cmd, pipeline.type, pipeline.layout, 0, 1, &vk.global_set.handle, cast(u32)offsets.length, offsets.ptr ); u32 offset_count; VkDescriptorSet[] handles = Alloc!(VkDescriptorSet)(&vk.frame_arenas[vk.frame_index], sets.length); foreach(i, set; sets) { handles[i] = set.handle; offset_count += set.dynamic_count; } if(offset_count > 0) { offsets = Alloc!(u32)(&vk.frame_arenas[vk.frame_index], offset_count); } vkCmdBindDescriptorSets( cmd, pipeline.type, pipeline.layout, 1, cast(u32)handles.length, handles.ptr, cast(u32)offsets.length, offsets.ptr ); } PipelineHandles* NewPipeline(Vulkan* vk) { PipelineHandles* pipeline = vk.pipeline_handles.ptr + vk.pipeline_count; pipeline.index = vk.pipeline_count; vk.pipeline_count += 1; return pipeline; } void Bind(Vulkan* vk, Pipeline pipeline_handle, DescSet set, bool compute = false) { assert(pipeline_handle > 0, "Bind failure: pipeline is 0"); VkCommandBuffer cmd = (compute ? vk.comp_cmd : vk.cmds[vk.frame_index]); PipelineHandles* pipeline = vk.pipeline_handles.ptr + pipeline_handle; BindPipeline(vk, cmd, pipeline); u32[] offsets; if(vk.global_set.dynamic_count > 0) { offsets = Alloc!(u32)(&vk.frame_arenas[vk.frame_index], vk.global_set.dynamic_count); } vkCmdBindDescriptorSets( cmd, pipeline.type, pipeline.layout, 0, 1, &vk.global_set.handle, cast(u32)offsets.length, offsets.ptr ); if(set.dynamic_count > 0) { offsets = Alloc!(u32)(&vk.frame_arenas[vk.frame_index], set.dynamic_count); } vkCmdBindDescriptorSets( cmd, pipeline.type, pipeline.layout, 1, 1, &set.handle, cast(u32)offsets.length, offsets.ptr ); } pragma(inline): void BindPipeline(Vulkan* vk, VkCommandBuffer cmd, PipelineHandles* pipeline) { vkCmdBindPipeline(cmd, pipeline.type, pipeline.handle); vk.last_pipeline[vk.frame_index] = pipeline.handle; VkViewport viewport = { x: 0.0, y: 0.0, width: cast(f32)vk.swapchain_extent.width, height: cast(f32)vk.swapchain_extent.height, minDepth: 0.0, maxDepth: 1.0, }; vkCmdSetViewport(cmd, 0, 1, &viewport); VkRect2D scissor = { extent: { width: vk.swapchain_extent.width, height: vk.swapchain_extent.height, }, }; vkCmdSetScissor(cmd, 0, 1, &scissor); } pragma(inline): void Transition(VkCommandBuffer cmd, VkImage image, VkImageLayout current_layout, VkImageLayout new_layout) { VkImageMemoryBarrier2 barrier = { sType: VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, srcStageMask: VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, srcAccessMask: VK_ACCESS_2_MEMORY_WRITE_BIT, dstStageMask: VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, dstAccessMask: VK_ACCESS_2_MEMORY_WRITE_BIT | VK_ACCESS_2_MEMORY_READ_BIT, oldLayout: current_layout, newLayout: new_layout, image: image, subresourceRange: { aspectMask: new_layout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT, baseMipLevel: 0, levelCount: VK_REMAINING_MIP_LEVELS, baseArrayLayer: 0, layerCount: VK_REMAINING_ARRAY_LAYERS, }, }; VkDependencyInfo dep_info = { sType: VK_STRUCTURE_TYPE_DEPENDENCY_INFO, imageMemoryBarrierCount: 1, pImageMemoryBarriers: &barrier, }; vkCmdPipelineBarrier2(cmd, &dep_info); } void ImageDescCheck(Descriptor* desc) { ImageDescCheck(desc.type); } void ImageDescCheck(DescType desc_type) { debug { bool match; enum types = [DT.CombinedSampler, DT.Image, DT.StorageImage, DT.InputAttach]; static foreach(type; types) { match |= desc_type == type; } assert(match, "Unable to transition non image"); } } void Transition(VkCommandBuffer cmd, Descriptor* desc, VkImageLayout new_layout) { ImageDescCheck(desc); Transition(cmd, &desc.view, new_layout); } pragma(inline): void Transition(VkCommandBuffer cmd, ImageView* view, VkImageLayout new_layout) { Transition(cmd, view.image, view.layout, new_layout); view.layout = new_layout; } pragma(inline): void Transition(VkCommandBuffer cmd, Image* image, VkImageLayout new_layout) { Transition(cmd, image.image, image.layout, new_layout); image.layout = new_layout; } bool BuildShader(Vulkan* vk, Shader* shader, u8[] bytes) { 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); return VkCheck("vkCreateShaderModule failure", result); } bool CreateGraphicsPipeline(Vulkan* vk, Pipeline* pipeline_handle, GfxPipelineInfo* build_info) { PipelineHandles* pipeline = NewPipeline(vk); pipeline.type = VK_PIPELINE_BIND_POINT_GRAPHICS; pipeline.layout = build_info.layout; VkDynamicState[2] dyn_state = [ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR ]; VkPipelineDynamicStateCreateInfo dyn_info = { sType: VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, pDynamicStates: dyn_state.ptr, dynamicStateCount: cast(u32)dyn_state.length, }; VkPipelineInputAssemblyStateCreateInfo assembly_info = { sType: VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, topology: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, primitiveRestartEnable: VK_FALSE, }; VkPipelineRasterizationStateCreateInfo rasterization_info = { sType: VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, cullMode: VK_CULL_MODE_BACK_BIT, polygonMode: VK_POLYGON_MODE_FILL, lineWidth: 1.0, frontFace: cast(VkFrontFace)build_info.front_face, }; VkPipelineMultisampleStateCreateInfo multisample_info = { sType: VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, rasterizationSamples: VK_SAMPLE_COUNT_1_BIT, minSampleShading: 1.0, alphaToCoverageEnable: VK_FALSE, alphaToOneEnable: VK_FALSE, }; VkPipelineDepthStencilStateCreateInfo depth_info = { sType: VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, depthTestEnable: VK_TRUE, depthWriteEnable: VK_TRUE, depthCompareOp: VK_COMPARE_OP_GREATER_OR_EQUAL, depthBoundsTestEnable: VK_FALSE, stencilTestEnable: VK_FALSE, minDepthBounds: 0.0, maxDepthBounds: 1.0, }; VkPipelineRenderingCreateInfo rendering_info = { sType: VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO, colorAttachmentCount: 1, pColorAttachmentFormats: cast(VkFormat*)&vk.draw_image.view.format, depthAttachmentFormat: vk.depth_image.view.format, }; VkPipelineColorBlendAttachmentState blend_state = { colorWriteMask: VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, blendEnable: VK_TRUE, srcColorBlendFactor: build_info.src_color, dstColorBlendFactor: build_info.dst_color, colorBlendOp: build_info.color_op, srcAlphaBlendFactor: build_info.src_alpha, dstAlphaBlendFactor: build_info.dst_alpha, alphaBlendOp: build_info.alpha_op, }; VkPipelineColorBlendStateCreateInfo blend_info = { sType: VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, logicOpEnable: VK_FALSE, logicOp: VK_LOGIC_OP_COPY, attachmentCount: 1, pAttachments: &blend_state, }; VkVertexInputBindingDescription vertex_input_desc = { binding: 0, inputRate: cast(VkVertexInputRate)build_info.input_rate, stride: build_info.input_rate_stride, }; VkPipelineVertexInputStateCreateInfo input_info = { sType: VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, vertexBindingDescriptionCount: 1, pVertexBindingDescriptions: &vertex_input_desc, vertexAttributeDescriptionCount: cast(u32)build_info.vertex_attributes.length, pVertexAttributeDescriptions: build_info.vertex_attributes.ptr, }; VkPipelineViewportStateCreateInfo viewport_info = { sType: VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, viewportCount: 1, scissorCount: 1, }; VkPipelineShaderStageCreateInfo[2] shader_info = [ { sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, stage: VK_SHADER_STAGE_FRAGMENT_BIT, pName: "main", }, { sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, stage: VK_SHADER_STAGE_VERTEX_BIT, pName: "main", }, ]; Arena* arena = &vk.frame_arenas[0]; bool success = true; Shader frag_module, vert_module; success &= BuildShader(vk, &frag_module, build_info.frag_shader); success &= BuildShader(vk, &vert_module, build_info.vertex_shader); scope(exit) { Destroy(vk, frag_module); Destroy(vk, vert_module); } if(success) { __traits(getMember, shader_info.ptr + 0, "module") = frag_module; __traits(getMember, shader_info.ptr + 1, "module") = vert_module; VkSpecializationInfo vert_spec_info = { dataSize: build_info.vert_spec.size, mapEntryCount: cast(u32)build_info.vert_spec.entries.length, pMapEntries: build_info.vert_spec.entries.ptr, pData: build_info.vert_spec.data, }; if(build_info.vert_spec.entries.length > 0) { shader_info[0].pSpecializationInfo = &vert_spec_info; } VkSpecializationInfo frag_spec_info = { dataSize: build_info.frag_spec.size, mapEntryCount: cast(u32)build_info.frag_spec.entries.length, pMapEntries: build_info.frag_spec.entries.ptr, pData: build_info.frag_spec.data, }; if(build_info.frag_spec.entries.length > 0) { shader_info[1].pSpecializationInfo = &frag_spec_info; } VkGraphicsPipelineCreateInfo create_info = { sType: VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, pNext: &rendering_info, pVertexInputState: &input_info, pInputAssemblyState: &assembly_info, pViewportState: &viewport_info, pRasterizationState: &rasterization_info, pMultisampleState: &multisample_info, pColorBlendState: &blend_info, pDepthStencilState: &depth_info, pDynamicState: &dyn_info, stageCount: cast(u32)shader_info.length, pStages: shader_info.ptr, renderPass: vk.render_pass, layout: build_info.layout, }; VkResult result = vkCreateGraphicsPipelines(vk.device, null, 1, &create_info, null, &pipeline.handle); success = VkCheck("CreateGraphicsPipeline failure", result); *pipeline_handle = pipeline.index; } return success; } Pipeline CreateComputePipeline(Vulkan* vk, CompPipelineInfo* comp_info) { VkComputePipelineCreateInfo info = { sType: VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, layout: comp_info.layout, stage: { sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, stage: VK_SHADER_STAGE_COMPUTE_BIT, pName: "main", }, }; Shader comp_module; BuildShader(vk, &comp_module, comp_info.shader); scope(exit) Destroy(vk, comp_module); __traits(getMember, &info.stage, "module") = comp_module; VkSpecializationInfo spec_info = { dataSize: comp_info.spec.size, mapEntryCount: cast(u32)comp_info.spec.entries.length, pMapEntries: comp_info.spec.entries.ptr, pData: comp_info.spec.data, }; if(comp_info.spec.entries.length > 0) { info.stage.pSpecializationInfo = &spec_info; } PipelineHandles* pipeline = NewPipeline(vk); pipeline.type = VK_PIPELINE_BIND_POINT_COMPUTE; pipeline.layout = comp_info.layout; Pipeline pipeline_handle = pipeline.index; VkResult result = vkCreateComputePipelines(vk.device, null, 1, &info, null, &pipeline.handle); VkCheck("CreateComputePipeline failure", result); return pipeline_handle; } void ClearDepth(T)(Vulkan* vk, T color) { static if(is(T.v : f32[4])) { ClearDepth(vk, color.v); } else static assert(false, "Invalid type for clear depth"); } void ClearColor(T)(Vulkan* vk, T color) { static if(is(T.v : f32[4])) { ClearColor(vk, color.v); } else static assert(false, "Invalid type for clear depth"); } void ClearDepth(Vulkan* vk, f32[4] color = [0.0, 0.0, 0.0, 0.0]) { Transition(vk.cmds[vk.frame_index], &vk.depth_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); ClearColor(vk, &vk.depth_image, color); } void ClearColor(Vulkan* vk, f32[4] color) { Transition(vk.cmds[vk.frame_index], &vk.draw_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); ClearColor(vk, &vk.draw_image, color); } void ClearColor(Vulkan* vk, Descriptor* desc, f32[4] color) { ImageDescCheck(desc); ClearColor(vk, &desc.view, color); } void ClearColor(Vulkan* vk, ImageView* view, f32[4] color) { VkImageSubresourceRange clear_range = { aspectMask: (view.depth_image ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT), levelCount: 1, layerCount: 1, }; vkCmdClearColorImage( vk.cmds[vk.frame_index], view.image, view.layout, cast(VkClearColorValue*)color, 1, &clear_range ); } void WaitIdle(Vulkan* vk) { vkDeviceWaitIdle(vk.device); } void Destroy(Vulkan* vk, Shader shader) { vkDestroyShaderModule(vk.device, shader, null); } void Destroy(Vulkan* vk, Pipeline pipeline) { vkDestroyPipeline(vk.device, vk.pipeline_handles[pipeline].handle, null); } void DestroyDescriptorPools(Vulkan* vk) { vkDestroyDescriptorPool(vk.device, vk.active_pool, null); Node!(VkDescriptorPool)* node = vk.full_pools.first; for(;;) { if(node == null) { break; } vkDestroyDescriptorPool(vk.device, node.value, null); node = node.next; } } void PushDescriptorPool(Vulkan* vk) { VkDescriptorPoolSize[8] pool_sizes = [ { type: VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, descriptorCount: 4096 }, { type: VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, descriptorCount: 4096 }, { type: VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, descriptorCount: 4096 }, { type: VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, descriptorCount: 4096 }, { type: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, descriptorCount: 4096 }, { type: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, descriptorCount: 4096 }, { type: VK_DESCRIPTOR_TYPE_SAMPLER, descriptorCount: 4096 }, { type: VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, descriptorCount: 4096 }, ]; VkDescriptorPoolCreateInfo pool_info = { sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, poolSizeCount: cast(u32)pool_sizes.length, pPoolSizes: pool_sizes.ptr, maxSets: MAX_SETS, }; VkDescriptorPool pool; VkResult result = vkCreateDescriptorPool(vk.device, &pool_info, null, &pool); bool success = VkCheck("vkCreateDescriptorPool failure", result); assert(success, "vkCreateDescriptorPool error"); if(vk.active_pool == null || vk.active_pool == VK_NULL_HANDLE) { Node!(VkDescriptorPool)* node = Alloc!(Node!(VkDescriptorPool)); node.value = vk.active_pool; PushFront(&vk.full_pools, node, null); } vk.active_pool = pool; } void PrepareWrite(Vulkan* vk, VkWriteDescriptorSet* write, DescSet set, Descriptor* desc) { Arena* arena = vk.frame_arenas.ptr + vk.frame_index; write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write.descriptorType = desc.type; write.dstSet = set.handle; write.dstBinding = desc.binding; write.descriptorCount = 1; switch(desc.type) with(DescType) { case Image, StorageImage, InputAttach: { VkDescriptorImageInfo* info = Alloc!(VkDescriptorImageInfo)(arena); info.imageView = desc.view.view; info.imageLayout = desc.view.shader_layout; write.pImageInfo = info; } break; case Uniform, Storage: { VkDescriptorBufferInfo* info = Alloc!(VkDescriptorBufferInfo)(arena); info.buffer = desc.buf.buffer; info.offset = cast(VkDeviceSize)0; info.range = cast(VkDeviceSize)desc.buf.size; write.pBufferInfo = info; } break; case UniformTexelBuf, StorageTexelBuf: { write.pTexelBufferView = &desc.buf_view.view; } break; case Sampler: { VkDescriptorImageInfo* info = Alloc!(VkDescriptorImageInfo)(arena); info.sampler = desc.sampler; write.pImageInfo = info; } break; default: assert(false, "Unsupported descriptor type"); } } void Write(Vulkan* vk, DescSet set, Descriptor[] descs) { Arena* arena = vk.frame_arenas.ptr + vk.frame_index; VkWriteDescriptorSet[] writes = Alloc!(VkWriteDescriptorSet)(arena, descs.length); for(u64 i = 0; i < descs.length; i += 1) { VkWriteDescriptorSet* write = writes.ptr + i; PrepareWrite(vk, write, set, &descs[i]); } vkUpdateDescriptorSets(vk.device, cast(u32)writes.length, writes.ptr, 0, null); } void Write(Vulkan* vk, DescSet set, Descriptor* desc) { VkWriteDescriptorSet write; PrepareWrite(vk, &write, set, desc); vkUpdateDescriptorSets(vk.device, 1, &write, 0, null); } void WriteConvDescriptor(Vulkan* vk, Buffer* buf) { VkDescriptorBufferInfo buf_info = { buffer: buf.buffer, range: buf.size, offset: 0, }; VkWriteDescriptorSet write = { sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, dstSet: vk.conv_desc_set.handle, dstBinding: 1, descriptorCount: 1, descriptorType: VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, pBufferInfo: &buf_info, }; vkUpdateDescriptorSets(vk.device, 1, &write, 0, null); } void WriteConvDescriptor(Vulkan* vk, ImageView* view) { VkDescriptorImageInfo image_info = { imageView: view.view, imageLayout: VK_IMAGE_LAYOUT_GENERAL, }; VkWriteDescriptorSet write = { sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, dstSet: vk.conv_desc_set.handle, dstBinding: 0, descriptorCount: 1, descriptorType: VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pImageInfo: &image_info, }; vkUpdateDescriptorSets(vk.device, 1, &write, 0, null); } pragma(inline): void Dispatch(Vulkan* vk, VkCommandBuffer cmd) { f32 w = Ceil(cast(f32)(vk.swapchain_extent.width) / 16.0F); f32 h = Ceil(cast(f32)(vk.swapchain_extent.height) / 16.0F); vkCmdDispatch(cmd, cast(u32)w, cast(u32)h, 1); } pragma(inline): void Dispatch(Vulkan* vk) { Dispatch(vk, vk.cmds[vk.frame_index]); } bool VkCheck(string message, VkResult result) { bool success = true; // TODO: Handle error cases that can be handled if(result == VK_ERROR_OUT_OF_DEVICE_MEMORY) { assert(false, "Handle VK_ERROR_OUT_OF_DEVICE_MEMORY"); } else if(result == VK_ERROR_OUT_OF_HOST_MEMORY) { assert(false, "Handle VK_ERROR_OUT_OF_HOST_MEMORY"); } else if(result != VK_SUCCESS) { success = false; char[512] buf; buf[] = '\0'; buf.sformat("%s: %s", message, VkResultStr(result)); Logf("%r", buf); } return success; } void VkCheckA(string message, VkResult result) { assert(VkCheck(message, result), "Aborting program due to failure"); } Format 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, IU.Draw, 0, &props ); if(result == VK_ERROR_FORMAT_NOT_SUPPORTED) { continue; } if(result == VK_SUCCESS) { selected_format = format; break; } } return cast(Format)selected_format; } void SelectSwapchainFormats(Vulkan* vk) { Arena* arena = &vk.frame_arenas[0]; u32 format_count; vkGetPhysicalDeviceSurfaceFormatsKHR(vk.physical_device, vk.surface, &format_count, null); VkSurfaceFormatKHR[] formats = Alloc!(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 = Alloc!(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; } } VkSurfaceFormatKHR surface_format = formats[0]; foreach(format; formats) { if(format.format == VK_FORMAT_B8G8R8A8_UNORM) { surface_format = format; break; } } vk.surface_format = surface_format; vk.present_mode = present_mode; } void CheckAndRecreateSwapchain(Vulkan* vk) { VkResult result = vkGetSwapchainStatusKHR(vk.device, vk.swapchain); if(result == VK_ERROR_OUT_OF_DATE_KHR) { RecreateSwapchain(vk); } else if(result != VK_SUBOPTIMAL_KHR) { VkCheckA("BeginFrame failure: vkAcquireNextImageKHR error", result); } } Descriptor CreateSampler(Vulkan* vk, MipmapMode mode) { 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: mode, }; Descriptor sampler = { type: DT.Sampler }; VkResult result = vkCreateSampler(vk.device, &sampler_info, null, &sampler.sampler); VkCheckA("vkCreateSampler failure", result); return sampler; } bool CreateDrawImages(Vulkan* vk) { bool success = true; Format draw_format = cast(Format)GetDrawImageFormat(vk); Format depth_format = cast(Format)VK_FORMAT_D32_SFLOAT; u32 w = vk.swapchain_extent.width; u32 h = vk.swapchain_extent.height; CreateImageView(vk, &vk.draw_image, w, h, draw_format, IU.Draw, DT.StorageImage); CreateImageView(vk, &vk.depth_image, w, h, depth_format, IU.Depth, DT.Image); return success; } bool CreateSwapchain(Vulkan* vk) { bool success = true; 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: IU.Swapchain, 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 = Alloc!(VkImage)(arena, count); vkGetSwapchainImagesKHR(vk.device, vk.swapchain, &count, images.ptr); VkImageView[] views = Alloc!(VkImageView)(arena, count); vk.present_images = Alloc!(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, }, }; VkFramebufferCreateInfo framebuffer_info = { sType: VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, }; foreach(i, image; vk.present_images) { vk.present_images[i].image = images[i]; vk.present_images[i].format = cast(Format)vk.surface_format.format; 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; } void RecreateSwapchain(Vulkan* vk) { vkDeviceWaitIdle(vk.device); Destroy(vk.swapchain, vk.present_images, vk.device); Destroy(vk, &vk.draw_image); Destroy(vk, &vk.depth_image); vkDestroyFramebuffer(vk.device, vk.framebuffer, null); CreateSwapchain(vk); CreateDrawImages(vk); Write(vk, vk.global_set, &vk.draw_image); CreateFramebuffer(vk); } void CreateFramebuffer(Vulkan* vk) { VkFramebufferCreateInfo framebuffer_info = { sType: VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, renderPass: vk.render_pass, attachmentCount: 2, pAttachments: [vk.draw_image.view.view, vk.depth_image.view.view], width: vk.swapchain_extent.width, height: vk.swapchain_extent.height, layers: 1, }; VkResult result = vkCreateFramebuffer(vk.device, &framebuffer_info, null, &vk.framebuffer); VkCheckA("vkCreateFramebuffer failure", result); } void Destroy(T)(Vulkan* vk, MappedBuffer!(T)* buf) { vmaUnmapMemory(vk.vma, buf.alloc); Destroy(vk, &buf.base); } void Destroy(Vulkan* vk, Buffer* buf) { vmaDestroyBuffer(vk.vma, buf.buffer, buf.alloc); } void Destroy(Vulkan* vk, Descriptor* desc) { switch(desc.type) { case DT.Sampler: { vkDestroySampler(vk.device, desc.sampler, null); } break; case DT.CombinedSampler, DT.Image, DT.StorageImage, DT.InputAttach: { if(desc.view.view) { vkDestroyImageView(vk.device, desc.view.view, null); } if(desc.view.image) { vmaDestroyImage(vk.vma, desc.view.image, desc.view.alloc); } } break; case DT.UniformTexelBuf, DT.StorageTexelBuf: { if(desc.buf_view.view) { vkDestroyBufferView(vk.device, desc.buf_view.view, null); } if(desc.buf_view.buffer) { vmaDestroyBuffer(vk.vma, desc.buf_view.buffer, desc.buf_view.alloc); } } break; case DT.Uniform, DT.DynamicUniform, DT.Storage, DT.DynamicStorage: { if(desc.buf.buffer) { vmaDestroyBuffer(vk.vma, desc.buf.buffer, desc.buf.alloc); } } break; default: assert(false, "Unsupported descriptor Destroy call"); } } void DestroyRenderer(Vulkan* vk) { foreach(i, arena; vk.frame_arenas) { Free(vk.frame_arenas.ptr + i); } Free(&vk.arena); } void Destroy(VkInstance instance) { if(instance) { vkDestroyInstance(instance, null); } } void Destroy(VkDebugUtilsMessengerEXT dbg, VkInstance instance) { version(VULKAN_DEBUG) if(dbg) { vkDestroyDebugUtilsMessengerEXT(instance, dbg, null); } } void Destroy(VkSurfaceKHR surface, VkInstance instance) { if(surface) { vkDestroySurfaceKHR(instance, surface, null); } } // Keep 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 PrintShaderDisassembly(Vulkan* vk, Pipeline pipeline_id, VkShaderStageFlagBits stage) { version(AMD_GPU) { PipelineHandles* pipeline = vk.pipeline_handles.ptr + pipeline_id; version(VULKAN_DEBUG) { u64 size; VkResult result = vkGetShaderInfoAMD(vk.device, pipeline.handle, stage, VK_SHADER_INFO_TYPE_DISASSEMBLY_AMD, &size, null); if(result == VK_SUCCESS) { u8[] buf = Alloc!(u8)(&vk.frame_arenas[vk.frame_index], size); vkGetShaderInfoAMD(vk.device, pipeline.handle, stage, VK_SHADER_INFO_TYPE_DISASSEMBLY_AMD, &size, buf.ptr); Logf("DISASSEMBLY:\n%r", buf); } } } } Vulkan Init(PlatformHandles platform_handles, u64 permanent_mem, u64 frame_mem) { bool success; Vulkan vk = { arena: CreateArena(permanent_mem), frame_arenas: [ CreateArena(frame_mem), CreateArena(frame_mem), ], platform_handles: platform_handles, }; Library lib; Function fn; foreach(name; VULKAN_LIBS) { lib = LoadLibrary(name); if(lib.ptr) { fn = LoadFunction(lib, "vkGetInstanceProcAddr"); vkGetInstanceProcAddr = cast(PFN_vkGetInstanceProcAddr)fn.ptr; } } if(fn.ptr) { vkGetInstanceProcAddr = cast(PFN_vkGetInstanceProcAddr)vkGetInstanceProcAddr(null, "vkGetInstanceProcAddr"); assert(vkGetInstanceProcAddr != null, "LoadGlobalFunctions failure: Unable to load vkGetInstanceProcAddr"); vkCreateInstance = cast(PFN_vkCreateInstance)vkGetInstanceProcAddr(null, "vkCreateInstance"); assert(vkCreateInstance != null, "LoadGlobalFunctions failure: Unable to load VkCreateInstance"); vkEnumerateInstanceLayerProperties = cast(PFN_vkEnumerateInstanceLayerProperties)vkGetInstanceProcAddr(null, "vkEnumerateInstanceLayerProperties"); assert(vkEnumerateInstanceLayerProperties != null, "LoadGlobalFunctions failure: Unable to load vkEnumerateInstanceLayerProperties"); success = true; } if(success) { Arena* arena = &vk.frame_arenas[0]; u32 count; vkEnumerateInstanceLayerProperties(&count, null); VkLayerProperties[] layers = Alloc!(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, 2, 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, }; VkValidationFeatureEnableEXT validation_enable = VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT; VkValidationFeaturesEXT validation_features = { sType: VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT, enabledValidationFeatureCount: 1, pEnabledValidationFeatures: &validation_enable, }; version(VULKAN_DEBUG) if(g_VLAYER_SUPPORT && g_DEBUG_PRINTF) { instance_info.pNext = &validation_features; } VkResult result = vkCreateInstance(&instance_info, null, &vk.instance); success &= VkCheck("vkCreateInstance failure", result); } if(success) { LoadInstanceFunctions(&vk); version (VULKAN_DEBUG) { if(g_VLAYER_SUPPORT) { VkDebugUtilsMessageSeverityFlagBitsEXT severity_flags = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; if(g_DEBUG_PRINTF) { severity_flags |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; } VkDebugUtilsMessengerCreateInfoEXT info = { sType: VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, messageSeverity: severity_flags, 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, }; if(g_DEBUG_PRINTF) { info.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; } 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"); } } } // Surface if(success) { version(linux) { VkXcbSurfaceCreateInfoKHR surface_info = { sType: VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR, connection: vk.platform_handles.conn, window: vk.platform_handles.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.platform_handles.instance, hwnd: vk.platform_handles.handle, }; VkResult result = vkCreateWin32SurfaceKHR(vk.instance, &surface_info, null, &vk.surface); } success &= VkCheck("InitSurface failure", result); } if(success) { success = false; Arena* arena = &vk.frame_arenas[0]; u32 count; vkEnumeratePhysicalDevices(vk.instance, &count, null); VkPhysicalDevice[] devices = Alloc!(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) { 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, }; count = 0; vkGetPhysicalDeviceQueueFamilyProperties(dev, &count, null); VkQueueFamilyProperties[] properties = Alloc!(VkQueueFamilyProperties)(arena, count); vkGetPhysicalDeviceQueueFamilyProperties(dev, &count, properties.ptr); if(count == 1 && properties[0].queueCount == 1 && properties[0].queueFlags & (T_BIT | C_BIT | G_BIT)) { current.gfx_index = current.tfer_index = 0; current.single_queue = true; } else { bool sparse = false; foreach(i, prop; properties) { i32 idx = cast(i32)i; b32 surface_support; vkGetPhysicalDeviceSurfaceSupportKHR(dev, cast(u32)i, vk.surface, &surface_support); if(current.gfx_index < 0 && surface_support && prop.queueFlags & G_BIT) { current.gfx_index = idx; continue; } if(BitEq(prop.queueFlags, T_BIT | S_BIT)) { sparse = true; current.tfer_index = idx; continue; } if(!sparse && prop.queueFlags & T_BIT) { current.tfer_index = idx; continue; } } if(current.tfer_index < 0) { current.tfer_index = current.gfx_index; } } if(current.gfx_index < 0) { continue; } bool discrete; bool result; // Properties VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(dev, &props); if(VK_API_VERSION_MINOR(props.apiVersion) >= 2) { u32 ext_count; vkEnumerateDeviceExtensionProperties(dev, null, &ext_count, null); VkExtensionProperties[] ext_props = Alloc!(VkExtensionProperties)(arena, ext_count); vkEnumerateDeviceExtensionProperties(dev, 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(dev, vk.surface, &fmt_count, null); vkGetPhysicalDeviceSurfacePresentModesKHR(dev, vk.surface, &present_count, null); discrete = props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; result = fmt_count && present_count; } } if(discrete_candidate && !discrete) { continue; } if(!result) { continue; } // Device features VkPhysicalDeviceFeatures2 features2 = { sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 }; VkPhysicalDeviceVulkan12Features features_12 = { sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES }; features2.pNext = &features_12; vkGetPhysicalDeviceFeatures2(dev, &features2); VkPhysicalDeviceFeatures features = features2.features; result &= cast(bool)features.fragmentStoresAndAtomics; 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.descriptorBindingStorageTexelBufferUpdateAfterBind; 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_12.storageBuffer8BitAccess; if(!result) { continue; } discrete_candidate = cast(bool)discrete; candidate = current; physical_device = dev; } 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; } VkPhysicalDeviceSynchronization2Features synchronization2 = { sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES, synchronization2: VK_TRUE, }; VkPhysicalDeviceVulkan12Features features_12 = { sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, pNext: &synchronization2, descriptorIndexing: VK_TRUE, bufferDeviceAddress: VK_TRUE, descriptorBindingUniformBufferUpdateAfterBind: VK_TRUE, descriptorBindingSampledImageUpdateAfterBind: VK_TRUE, descriptorBindingStorageImageUpdateAfterBind: VK_TRUE, descriptorBindingStorageBufferUpdateAfterBind: VK_TRUE, descriptorBindingStorageTexelBufferUpdateAfterBind: VK_TRUE, descriptorBindingPartiallyBound: VK_TRUE, shaderSampledImageArrayNonUniformIndexing: VK_TRUE, shaderUniformBufferArrayNonUniformIndexing: VK_TRUE, runtimeDescriptorArray: VK_TRUE, storageBuffer8BitAccess: 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, fragmentStoresAndAtomics: 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; } } } if(success) { VmaVulkanFunctions vk_functions = { vkGetInstanceProcAddr: vkGetInstanceProcAddr, vkGetDeviceProcAddr: vkGetDeviceProcAddr, }; VmaAllocatorCreateInfo info = { flags: VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT, vulkanApiVersion: VK_MAKE_API_VERSION(0, 1, 2, 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); } if(success) { success = CreateSwapchain(&vk); } if(success) { success = CreateDrawImages(&vk); } if(success) { 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, }; u32 sem_count = cast(u32)vk.present_images.length; vk.submit_sems = Alloc!(VkSemaphore)(arena, sem_count); VkResult result; foreach(i; 0 .. sem_count) { result = vkCreateSemaphore(vk.device, &sem_info, null, vk.submit_sems.ptr + i); success &= VkCheck("vkCreateSemaphore failure", result); } foreach(i; 0 .. FRAME_OVERLAP) { pool_info.queueFamilyIndex = vk.gfx_index; result = vkCreateCommandPool(vk.device, &pool_info, null, vk.cmd_pools.ptr + i); success &= VkCheck("vkCreateCommandPool failure", result); cmd_info.commandPool = vk.cmd_pools[i]; result = vkAllocateCommandBuffers(vk.device, &cmd_info, vk.cmds.ptr + i); success &= VkCheck("vkAllocateCommandBuffers failure", result); result = vkCreateFence(vk.device, &fence_info, null, vk.render_fences.ptr + i); success &= VkCheck("vkCreateFence failure", result); result = vkCreateSemaphore(vk.device, &sem_info, null, vk.acquire_sems.ptr + i); success &= VkCheck("vkCreateSemaphore failure", result); } pool_info.queueFamilyIndex = vk.tfer_index; result = vkCreateCommandPool(vk.device, &pool_info, null, &vk.imm_pool); success &= VkCheck("vkCreateCommandPool failure", result); cmd_info.commandPool = vk.imm_pool; result = vkAllocateCommandBuffers(vk.device, &cmd_info, &vk.imm_cmd); success &= VkCheck("vkAllocateCommandBuffers failure", result); result = vkCreateFence(vk.device, &fence_info, null, &vk.imm_fence); success &= VkCheck("vkCreateFence failure", result); pool_info.queueFamilyIndex = vk.gfx_index; result = vkCreateCommandPool(vk.device, &pool_info, null, &vk.comp_cmd_pool); success &= VkCheck("vkCreateCommandPool failure", result); cmd_info.commandPool = vk.comp_cmd_pool; result = vkAllocateCommandBuffers(vk.device, &cmd_info, &vk.comp_cmd); success &= VkCheck("vkCreateCommandPool failure", result); result = vkCreateFence(vk.device, &fence_info, null, &vk.comp_fence); success &= VkCheck("vkCreateFence failure", result); } if(success) { PushDescriptorPool(&vk); if(success) { DescLayoutBinding[2] layout_bindings = [ { binding: 0, descriptorType: VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, descriptorCount: 1, stageFlags: VK_SHADER_STAGE_ALL }, { binding: 1, descriptorType: VK_DESCRIPTOR_TYPE_SAMPLER, descriptorCount: 1, stageFlags: VK_SHADER_STAGE_ALL }, ]; vk.global_set_layout = CreateDescSetLayout(&vk, layout_bindings); vk.global_set = AllocDescSet(&vk, vk.global_set_layout); Write(&vk, vk.global_set, [vk.draw_image]); } } if(success) { vk.pipeline_handles = Alloc!(PipelineHandles)(&vk.arena, 128); vk.pipeline_count += 1; u64 transfer_size = MB(64); vk.transfer_buf = CreateMappedBuffer!(u8)(&vk, BT.Staging, transfer_size); VkDescriptorSetLayoutBinding[2] layout_bindings = [ { binding: 0, descriptorType: VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, descriptorCount: 1, stageFlags: VK_SHADER_STAGE_COMPUTE_BIT }, { binding: 1, descriptorType: VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, descriptorCount: 1, stageFlags: VK_SHADER_STAGE_COMPUTE_BIT }, ]; vk.conv_desc_layout = CreateDescSetLayout(&vk, layout_bindings); vk.conv_desc_set = AllocDescSet(&vk, vk.conv_desc_layout); vk.conv_pipeline_layout = CreatePipelineLayout(&vk, vk.conv_desc_layout, ConvPushConst.sizeof, true); u32 channels = 1; SpecEntry[1] entries = [ { constantID: 0, size: u32.sizeof, offset: 0, } ]; CompPipelineInfo conv_info = { shader: CONVERT_SHADER_BYTES, layout: vk.conv_pipeline_layout, spec: { data: &channels, size: u32.sizeof, entries: entries, }, }; vk.r_to_rgba_pipeline = CreateComputePipeline(&vk, &conv_info); channels = 2; vk.rg_to_rgba_pipeline = CreateComputePipeline(&vk, &conv_info); channels = 3; vk.rgb_to_rgba_pipeline = CreateComputePipeline(&vk, &conv_info); VkAttachmentDescription[2] attach_descriptions = [ { format: vk.draw_image.view.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_COLOR_ATTACHMENT_OPTIMAL, finalLayout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }, { format: vk.depth_image.view.format, samples: VK_SAMPLE_COUNT_1_BIT, loadOp: VK_ATTACHMENT_LOAD_OP_CLEAR, storeOp: VK_ATTACHMENT_STORE_OP_STORE, initialLayout: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, finalLayout: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }, ]; VkAttachmentReference color_ref = { attachment: 0, layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }; VkAttachmentReference depth_ref = { attachment: 1, layout: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }; VkSubpassDescription subpass = { pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS, colorAttachmentCount: 1, pColorAttachments: &color_ref, pDepthStencilAttachment: &depth_ref, }; VkSubpassDependency self_dependency = { srcSubpass: 0, dstSubpass: 0, srcStageMask: VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, dstStageMask: VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, srcAccessMask: VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, dstAccessMask: VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, dependencyFlags: VK_DEPENDENCY_BY_REGION_BIT, }; VkRenderPassCreateInfo pass_info = { sType: VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, attachmentCount: cast(u32)attach_descriptions.length, pAttachments: attach_descriptions.ptr, subpassCount: 1, pSubpasses: &subpass, dependencyCount: 1, pDependencies: &self_dependency, }; VkResult result = vkCreateRenderPass(vk.device, &pass_info, null, &vk.render_pass); VkCheckA("vkCreateRenderPass failure", result); CreateFramebuffer(&vk); } assert(success, "Error initializing vulkan"); return vk; } unittest { PlatformHandles handles; Vulkan vk = Init(handles, 10*1000*1000, 10*1000*1000); }