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;
import std.math.rounding : Ceil = ceil;

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;

	u32												 frame_index;
	u32												 semaphore_index;

	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;
	u32												 image_index;

	ImageView							 	 	 draw_image;
	ImageView							 	 	 depth_image;

	VkCommandPool[FRAME_OVERLAP]	 cmd_pools;
	VkCommandBuffer[FRAME_OVERLAP] cmds;

	VkSemaphore[] 						 submit_sems;
	VkSemaphore[FRAME_OVERLAP] acquire_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 PipelineHandle
{
	VkPipeline 					handle;
	VkPipelineBindPoint type;
}

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;
}

VkImage
CurrentImage(Vulkan* vk)
{
	return vk.present_images[vk.image_index].base.image;
}

bool
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);
	bool success = VkCheck("BeginFrame failure: vkWaitForFences error", result);

	if (success)
	{
		result = vkResetFences(vk.device, 1, vk.render_fences.ptr + vk.frame_index);
		success = VkCheck("BeginFrame failure: vkResetFences error", result);
	}

	if (success)
	{
		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 
		{
			success = VkCheck("BeginFrame failure: vkAcquireNextImageKHR error", result);
		}
	}

	if (success)
	{
		result = vkResetCommandBuffer(vk.cmds[vk.frame_index], 0);
		success = VkCheck("BeginFrame failure: vkResetCommandBuffer failure", 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.cmds[vk.frame_index], &cmd_info);
		success = VkCheck("BeginFrame failure: vkBeginCommandBuffer error", result);
	}

	return success;
}

void
BeginRender(Vulkan* vk)
{
	// TODO: probably get rid of these
	Transition(vk.cmds[vk.frame_index], &vk.draw_image.base, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
	Transition(vk.cmds[vk.frame_index], &vk.depth_image.base, 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);

	VkRenderingAttachmentInfo col_attach = {
		sType: VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
		imageView: vk.draw_image.view,
		imageLayout: vk.draw_image.base.layout,
		loadOp: VK_ATTACHMENT_LOAD_OP_LOAD,
		storeOp: VK_ATTACHMENT_STORE_OP_STORE,
	};

	VkRenderingAttachmentInfo depth_attach = {
		sType: VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
		imageView: vk.depth_image.view,
		imageLayout: vk.depth_image.base.layout,
		loadOp: VK_ATTACHMENT_LOAD_OP_CLEAR,
		storeOp: VK_ATTACHMENT_STORE_OP_STORE,
	};

	VkRenderingInfo render_info = {
		sType: VK_STRUCTURE_TYPE_RENDERING_INFO,
		layerCount: 1,
		colorAttachmentCount: 1,
		pColorAttachments: &col_attach,
		pDepthAttachment: &depth_attach,
		renderArea: {
			extent: {
				width: vk.swapchain_extent.width,
				height: vk.swapchain_extent.height,
			},
		},
	};

	vkCmdBeginRendering(vk.cmds[vk.frame_index], &render_info);
}

void
PrepCompute(Vulkan* vk)
{
	Transition(vk.cmds[vk.frame_index], &vk.draw_image, VK_IMAGE_LAYOUT_GENERAL);
}

bool
FinishFrame(Vulkan* vk)
{
	scope(exit) vk.frame_index = (vk.frame_index + 1) % FRAME_OVERLAP;

	bool success = true;

	VkImage image = CurrentImage(vk);
	VkSemaphore acquire_sem = vk.acquire_sems[vk.frame_index];
	VkSemaphore submit_sem = vk.submit_sems[vk.image_index];

	vkCmdEndRendering(vk.cmds[vk.frame_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
	Copy(vk.cmds[vk.frame_index], vk.draw_image.base.image, image, 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]);
	success = VkCheck("FinishFrame failure: vkEndCommandBuffer error", result);

	if (success)
	{
		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]);
		success = VkCheck("FinishFrame failure: vkQueueSubmit2 error", result);
	}

	if (success)
	{
		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
		{
			success = VkCheck("FinishFrame failure: vkQueuePresentKHR failure", result);
		}
	}

	return success;
}

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)
{
	vkCmdDrawIndexed(vk.cmds[vk.frame_index], index_count, instance_count, 0, 0, 0);
}

pragma(inline): void
Copy(VkCommandBuffer cmd, VkImage src, VkImage dst, VkExtent2D src_ext, VkExtent2D dst_ext)
{
	VkImageBlit2 blit = {
		sType: VK_STRUCTURE_TYPE_IMAGE_BLIT_2,
		srcOffsets: [
			{},
			{ x: cast(i32)src_ext.width, y: cast(i32)src_ext.height, z: 1 },
		],
		dstOffsets: [
			{},
			{ 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,
		},
	};

	VkBlitImageInfo2 blit_info = {
		sType: VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2,
		srcImage: src,
		srcImageLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
		dstImage: dst,
		dstImageLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
		filter: VK_FILTER_LINEAR,
		regionCount: 1,
		pRegions: &blit,
	};

	vkCmdBlitImage2(cmd, &blit_info);
}

void
Bind(Vulkan* vk, PipelineHandle pipeline)
{
	vkCmdBindPipeline(vk.cmds[vk.frame_index], pipeline.type, pipeline.handle);

	vkCmdBindDescriptorSets(
		vk.cmds[vk.frame_index], 
		pipeline.type, 
		vk.pipeline_layout, 
		0, 
		cast(u32)vk.desc_sets.length, 
		vk.desc_sets.ptr,
		0,
		null
	);

	VkViewport viewport = {
		width: cast(f32)vk.swapchain_extent.width,
		height: cast(f32)vk.swapchain_extent.height,
		maxDepth: 1.0F,
	};

	vkCmdSetViewport(vk.cmds[vk.frame_index], 0, 1, &viewport);

	VkRect2D scissor = {
		extent: {
			width: vk.swapchain_extent.width,
			height: vk.swapchain_extent.height,
		},
	};

	vkCmdSetScissor(vk.cmds[vk.frame_index], 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);
}

pragma(inline): void
Transition(VkCommandBuffer cmd, ImageView* view, VkImageLayout new_layout)
{
	Transition(cmd, view.base.image, view.base.layout, new_layout);
	view.base.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;
}

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;
}

Pipeline
CreateGraphicsPipeline(Vulkan* vk, GfxPipelineInfo* build_info)
{
	VkDynamicState[] 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,
		polygonMode: VK_POLYGON_MODE_FILL,
		lineWidth: 1.0,
		frontFace: VK_FRONT_FACE_COUNTER_CLOCKWISE,
	};

	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: &vk.draw_image.base.format,
		depthAttachmentFormat: vk.depth_image.base.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_FALSE,
	};

	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[] shader_info = [
		{
			sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
			stage: VK_SHADER_STAGE_VERTEX_BIT,
			pName: "main",
		},
		{
			sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
			stage: VK_SHADER_STAGE_FRAGMENT_BIT,
			pName: "main",
		},
	];

	__traits(getMember, shader_info.ptr + 0, "module") = build_info.vertex_shader;
	__traits(getMember, shader_info.ptr + 1, "module") = build_info.frag_shader;

	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,
		layout: vk.pipeline_layout,
	};

	PipelineHandle pipeline = { type: VK_PIPELINE_BIND_POINT_GRAPHICS };
	
	VkResult result = vkCreateGraphicsPipelines(vk.device, null, 1, &create_info, null, &pipeline.handle);
	assert(VkCheck("CreateGraphicsPipeline failure", result), "Unable to build pipeline");

	return pipeline;
}


Pipeline
CreateComputePipeline(Vulkan* vk, Shader shader)
{
	VkComputePipelineCreateInfo info = {
		sType: VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
		layout: vk.pipeline_layout,
		stage: {
			sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
			stage: VK_SHADER_STAGE_COMPUTE_BIT,
			pName: "main",
		},
	};

	__traits(getMember, &info.stage, "module") = shader;

	PipelineHandle pipeline = { type: VK_PIPELINE_BIND_POINT_COMPUTE };
	VkResult result = vkCreateComputePipelines(vk.device, null, 1, &info, null, &pipeline.handle);
	assert(VkCheck("CreateComputePipeline failure", result), "Unable to build pipeline");

	return pipeline;
}

void
Destroy(Vulkan* vk, Shader shader)
{
	vkDestroyShaderModule(vk.device, shader, null);
}

void
Destroy(Vulkan* vk, Pipeline pipeline)
{
	vkDestroyPipeline(vk.device, pipeline.handle, null);
}

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,
		};

		VkDescriptorImageInfo draw_image_info = {
			imageView: vk.draw_image.view,
			imageLayout: VK_IMAGE_LAYOUT_GENERAL,
		};

		VkWriteDescriptorSet[] writes = [
			{
				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,
			},
			{
				sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
				dstSet: vk.desc_sets[DT.Shared],
				dstBinding: 2,
				descriptorCount: 1,
				descriptorType: VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
				pImageInfo: &draw_image_info,
			},
		];

		vkUpdateDescriptorSets(vk.device, cast(u32)writes.length, writes.ptr, 0, null);
	}

	return success;
}

void
Dispatch(Vulkan* vk)
{
	f32 w = Ceil(cast(f32)(vk.swapchain_extent.width) / 16.0F);
	f32 h = Ceil(cast(f32)(vk.swapchain_extent.height) / 16.0F);
	vkCmdDispatch(vk.cmds[vk.frame_index], cast(u32)w, cast(u32)h, 1);
}

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,
	};

	u32 sem_count = cast(u32)vk.present_images.length;
	vk.submit_sems = a.AllocArray!(VkSemaphore)(arena, sem_count);

	foreach(i; 0 .. sem_count)
	{
		if (success)
		{
			VkResult result = vkCreateSemaphore(vk.device, &sem_info, null, vk.submit_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.acquire_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;
}

void
RecreateSwapchain(Vulkan* vk)
{
	vkDeviceWaitIdle(vk.device);

	Destroy(vk.swapchain, vk.present_images, vk.device);

	CreateSwapchain(vk);
}

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(sem; vk.submit_sems)
	{
		if (sem)
		{
			vkDestroySemaphore(vk.device, sem, null);
		}
	}

	foreach(i; 0 .. FRAME_OVERLAP)
	{
		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);
		}

		if (vk.acquire_sems[i])
		{
			vkDestroySemaphore(vk.device, vk.acquire_sems[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");
		}
	}
}

