#if OS_WINDOWS

#define VK_PLATFORM_SURFACE_EXTENSION VK_KHR_WIN32_SURFACE_EXTENSION_NAME

static void *Vk_LoadLibrary() {
	void *result = LoadLibraryA("vulkan-1.dll");

	if (result != 0) {
		vk.GetInstanceProcAddr = (PFN_vkGetInstanceProcAddr) GetProcAddress(result, "vkGetInstanceProcAddr");
	}

	return result;
}

static void Vk_CreateSurface(SDL_Window *window) {
	SDL_PropertiesID prop_id = SDL_GetWindowProperties(window);

	VkWin32SurfaceCreateInfoKHR create_info = { 0 };
	create_info.sType     = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
	create_info.hinstance = SDL_GetPointerProperty(prop_id, SDL_PROP_WINDOW_WIN32_INSTANCE_POINTER, 0);
	create_info.hwnd      = SDL_GetPointerProperty(prop_id, SDL_PROP_WINDOW_WIN32_HWND_POINTER, 0);

	vk.err = vk.CreateWin32SurfaceKHR(vk.instance, &create_info, 0, &vk.surface);
}

#elif OS_LINUX

#define VK_PLATFORM_SURFACE_EXTENSION \
	VK_KHR_XLIB_SURFACE_EXTENSION_NAME, \
	VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME

static void *Vk_LoadLibrary() {
	void *result = dlopen("libvulkan.so", RTLD_LAZY);

	if (result != 0) {
		vk.GetInstanceProcAddr = (PFN_vkGetInstanceProcAddr) dlsym(result, "vkGetInstanceProcAddr");
	}

	return result;
}

static void Vk_CreateSurface(SDL_Window *window) {
	SDL_PropertiesID prop_id = SDL_GetWindowProperties(window);

	struct wl_display *wl_display = SDL_GetPointerProperty(prop_id, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, 0);
	if (wl_display == 0) {
		// Assume we are running via X11 if there isn't a vaild Wayland display
		//
		VkXlibSurfaceCreateInfoKHR create_info = { 0 };
		create_info.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
		create_info.dpy    = SDL_GetPointerProperty(prop_id, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, 0);
		create_info.window = SDL_GetNumberProperty(prop_id, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);

		vk.err = vk.CreateXlibSurfaceKHR(vk.instance, &create_info, 0, &vk.surface);
	}
	else {
		VkWaylandSurfaceCreateInfoKHR create_info = { 0 };
		create_info.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
		create_info.display = wl_display;
		create_info.surface = SDL_GetPointerProperty(prop_id, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, 0);

		vk.err = vk.CreateWaylandSurfaceKHR(vk.instance, &create_info, 0, &vk.surface);
	}
}

#endif

Vk_Context vk;

bool Vk_Setup(SDL_Window *window) {
	bool result = false;

	vk.lib = Vk_LoadLibrary();

	// Create instance
	//
	if (vk.err == VK_SUCCESS) {
		vk.CreateInstance = (PFN_vkCreateInstance) vk.GetInstanceProcAddr(0, "vkCreateInstance");

		const char *extensions[] = {
			VK_KHR_SURFACE_EXTENSION_NAME,
			VK_PLATFORM_SURFACE_EXTENSION,

			#if !LD_RELEASE
				VK_EXT_DEBUG_UTILS_EXTENSION_NAME
			#endif
		};

		const char *layers[] = {
			"VK_LAYER_KHRONOS_validation"
		};

		U32 n_layers = LD_RELEASE ? 0 : ArraySize(layers);

		VkApplicationInfo app = { 0 };
		app.sType      = VK_STRUCTURE_TYPE_APPLICATION_INFO;
		app.apiVersion = VK_API_VERSION_1_3; // I know 1.4 is out

		VkInstanceCreateInfo create_info = { 0 };
		create_info.sType                   = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
		create_info.pApplicationInfo        = &app;
		create_info.ppEnabledExtensionNames = extensions;
		create_info.enabledExtensionCount   = ArraySize(extensions);
		create_info.ppEnabledLayerNames     = layers;
		create_info.enabledLayerCount       = n_layers;

		vk.err = vk.CreateInstance(&create_info, 0, &vk.instance);
	}

	// Load instance level functions and create window surface
	//
	if (vk.err == VK_SUCCESS) {
		#define VK_INSTANCE_FUNCTIONS
		#define VK_FUNC(x) vk.x = (PFN_vk##x) vk.GetInstanceProcAddr(vk.instance, Stringify(Glue(vk, x)))
			#include "functions.h"
		#undef VK_FUNC

		Vk_CreateSurface(window);
	}

	// Select GPU
	//
	if (vk.err == VK_SUCCESS) {
		VkPhysicalDevice devices[8] = { 0 };
		U32 n_devices;

		vk.EnumeratePhysicalDevices(vk.instance, &n_devices, 0);
		vk.EnumeratePhysicalDevices(vk.instance, &n_devices, devices);

		vk.gpu = devices[0];

		for (U32 it = 0; it < n_devices; ++it) {
			VkPhysicalDeviceProperties props;
			vk.GetPhysicalDeviceProperties(devices[it], &props);

			if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
				vk.gpu = devices[it];
				break;
			}
		}

		if (vk.gpu != VK_NULL_HANDLE) {
			VkQueueFamilyProperties qprops[32] = { 0 };
			U32 n_qprops = 0;

			vk.GetPhysicalDeviceQueueFamilyProperties(vk.gpu, &n_qprops, 0);
			vk.GetPhysicalDeviceQueueFamilyProperties(vk.gpu, &n_qprops, qprops);

			for (U32 it = 0; it < n_qprops; ++it) {
				if (qprops[it].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
					// Assumed combined graphics-present queue, this is the case on all desktop class
					// gpus
					vk.queue.family = it;
					break;
				}
			}
		}
	}
	else {
		printf("[Error] :: Failed to create instance\n");
	}

	// Create logical device
	//
	if (vk.gpu != VK_NULL_HANDLE) {
		const char *extensions[] = {
			VK_KHR_SWAPCHAIN_EXTENSION_NAME
		};

		VkPhysicalDeviceVulkan11Features features11 = { 0 };
		features11.sType                              = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
		features11.storageBuffer16BitAccess           = VK_TRUE;
		features11.uniformAndStorageBuffer16BitAccess = VK_TRUE;

		VkPhysicalDeviceVulkan12Features features12 = { 0 };
		features12.sType                             = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
		features12.pNext                             = &features11;
		features12.shaderFloat16                     = VK_TRUE;
		features12.shaderInt8                        = VK_TRUE;
		features12.storageBuffer8BitAccess           = VK_TRUE;
		features12.uniformAndStorageBuffer8BitAccess = VK_TRUE;
		features12.descriptorIndexing                = VK_TRUE;
		features12.runtimeDescriptorArray            = VK_TRUE;

		// @Todo: we will probably need to enable some of the 'nonuniform' indexing features

		VkPhysicalDeviceVulkan13Features features13 = { 0 };
		features13.sType            = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
		features13.pNext            = &features12;
		features13.synchronization2 = VK_TRUE;
		features13.dynamicRendering = VK_TRUE;

		F32 priority = 1.0f;

		VkDeviceQueueCreateInfo queue_info = { 0 };
		queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
		queue_info.queueFamilyIndex = vk.queue.family;
		queue_info.queueCount       = 1;
		queue_info.pQueuePriorities = &priority;

		VkDeviceCreateInfo create_info = { 0 };
		create_info.sType                   = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
		create_info.pNext                   = &features13;
		create_info.queueCreateInfoCount    = 1;
		create_info.pQueueCreateInfos       = &queue_info;
		create_info.enabledExtensionCount   = ArraySize(extensions);
		create_info.ppEnabledExtensionNames = extensions;
		create_info.pEnabledFeatures        = 0;

		vk.err = vk.CreateDevice(vk.gpu, &create_info, 0, &vk.device);
	}
	else {
		printf("[Error] :: Failed to find suitable GPU\n");
		vk.err = VK_ERROR_DEVICE_LOST;
	}

	if (vk.err == VK_SUCCESS) {
		// Load device level functions and acquire the device queue
		#define VK_DEVICE_FUNCTIONS
		#define VK_FUNC(x) vk.x = (PFN_vk##x) vk.GetDeviceProcAddr(vk.device, Stringify(Glue(vk, x)))
			#include "functions.h"
		#undef VK_FUNC

		vk.GetDeviceQueue(vk.device, vk.queue.family, 0, &vk.queue.handle);

		// Create swapchain
		U32 n_images = 2;
		{
			VkSurfaceCapabilitiesKHR caps;
			vk.GetPhysicalDeviceSurfaceCapabilitiesKHR(vk.gpu, vk.surface, &caps);

			if (caps.currentExtent.width == -1) {
				int w, h;
				SDL_GetWindowSizeInPixels(window, &w, &h);

				vk.swapchain.width  = w;
				vk.swapchain.height = h;
			}
			else {
				vk.swapchain.width  = caps.currentExtent.width;
				vk.swapchain.height = caps.currentExtent.height;
			}

			n_images = Max(n_images, caps.minImageCount);
		}

		{
			VkSurfaceFormatKHR formats[32];
			U32 n_formats = 0;

			vk.GetPhysicalDeviceSurfaceFormatsKHR(vk.gpu, vk.surface, &n_formats, 0);
			vk.GetPhysicalDeviceSurfaceFormatsKHR(vk.gpu, vk.surface, &n_formats, formats);

			vk.swapchain.format = formats[0];

			for (U32 it = 0; it < n_formats; ++it) {
				VkSurfaceFormatKHR *fmt = &formats[it];

				if ((fmt->format == VK_FORMAT_B8G8R8A8_SRGB) ||
				    (fmt->format == VK_FORMAT_R8G8B8A8_SRGB))
				{
					if (fmt->colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
						vk.swapchain.format = formats[it];
						break;
					}
				}
			}
		}

		VkSwapchainCreateInfoKHR create_info = { 0 };
		create_info.sType            = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
		create_info.surface          = vk.surface;
		create_info.minImageCount    = n_images;
		create_info.imageFormat      = vk.swapchain.format.format;
		create_info.imageColorSpace  = vk.swapchain.format.colorSpace;
		create_info.imageExtent      = (VkExtent2D) { vk.swapchain.width, vk.swapchain.height };
		create_info.imageArrayLayers = 1;
		create_info.imageUsage       = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
		create_info.preTransform     = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
		create_info.compositeAlpha   = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
		create_info.presentMode      = VK_PRESENT_MODE_FIFO_KHR;
		create_info.clipped          = VK_TRUE;

		vk.err = vk.CreateSwapchainKHR(vk.device, &create_info, 0, &vk.swapchain.handle);

		vk.swapchain.n_images = n_images;
	}
	else {
		printf("[Error] :: Failed to create device\n");
	}

	if (vk.err == VK_SUCCESS) {
		// Get the swapchain images and create image views using them
		U32 n_images = 0;
		vk.GetSwapchainImagesKHR(vk.device, vk.swapchain.handle, &n_images, 0);
		vk.GetSwapchainImagesKHR(vk.device, vk.swapchain.handle, &n_images, vk.swapchain.images);

		vk.in_flight = n_images + 1;

		if (n_images != vk.swapchain.n_images) {
			printf("[Warn] :: Swapchain image count mismatch\n");
			vk.swapchain.n_images = n_images;
		}
		else if (n_images >= VK_MAX_FRAMES_IN_FLIGHT) {
			printf("[Warn] :: Min image count too high: %d\n", n_images);
			vk.in_flight = VK_MAX_FRAMES_IN_FLIGHT;
		}

		VkImageViewCreateInfo create_info = { 0 };
		create_info.sType    = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
		create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
		create_info.format   = vk.swapchain.format.format;

		create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
		create_info.subresourceRange.levelCount = 1;
		create_info.subresourceRange.layerCount = 1;

		for (U32 it = 0; it < n_images && vk.err == VK_SUCCESS; ++it) {
			create_info.image = vk.swapchain.images[it];
			vk.err = vk.CreateImageView(vk.device, &create_info, 0, &vk.swapchain.views[it]);
		}
	}

	for (U32 it = 0; it < vk.in_flight && vk.err == VK_SUCCESS; ++it) {
		Vk_Frame *frame = &vk.frames[it];

		VkCommandPoolCreateInfo pool = { 0 };
		pool.sType            = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
		pool.flags            = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
		pool.queueFamilyIndex = vk.queue.family;

		VkResult err = vk.CreateCommandPool(vk.device, &pool, 0, &frame->pool);

		// Base command buffer
		VkCommandBufferAllocateInfo alloc = { 0 };
		alloc.sType              = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
		alloc.commandBufferCount = 1;
		alloc.level              = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
		alloc.commandPool        = frame->pool;

		vk.AllocateCommandBuffers(vk.device, &alloc, &frame->cmd);

		// Scratch command buffers
		VkCommandBuffer scratch[VK_NUM_SCRATCH];
		alloc.commandBufferCount = VK_NUM_SCRATCH;

		vk.AllocateCommandBuffers(vk.device, &alloc, scratch);

		VkFenceCreateInfo fence = { 0 };
		fence.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
		fence.flags = VK_FENCE_CREATE_SIGNALED_BIT;

		for (U32 s = 0; s < VK_NUM_SCRATCH; ++s) {
			frame->scratch[s].handle = scratch[s];
			err = Min(vk.CreateFence(vk.device, &fence, 0, &frame->scratch[s].fence), err);
		}

		// Descriptor pool
		VkDescriptorPoolSize pool_sizes[2] = { 0 };

		pool_sizes[0].type            = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
		pool_sizes[0].descriptorCount = 2048;

		pool_sizes[1].type            = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
		pool_sizes[1].descriptorCount = 2048;

		VkDescriptorPoolCreateInfo descriptor_pool = { 0 };
		descriptor_pool.sType         = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
		descriptor_pool.maxSets       = 1024;
		descriptor_pool.poolSizeCount = 2;
		descriptor_pool.pPoolSizes    = pool_sizes;

		vk.CreateDescriptorPool(vk.device, &descriptor_pool, 0, &frame->descriptors);

		// rect buffer
		//

		Vk_Buffer *rbo = &frame->rbo;
		rbo->usage        = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
		rbo->size         = D_MAX_RECTS * sizeof(D_Rect);
		rbo->host_visible = true;

		Vk_BufferCreate(rbo);

		VkSemaphoreCreateInfo semaphore = { 0 };
		semaphore.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;

		err = Min(vk.CreateSemaphore(vk.device, &semaphore, 0, &frame->acquire),  err);
		err = Min(vk.CreateSemaphore(vk.device, &semaphore, 0, &frame->complete), err);
		err = Min(vk.CreateFence(vk.device, &fence, 0, &frame->fence), err);

		vk.err = err;
	}

	{
		VkSamplerCreateInfo create_info = { 0 };
		create_info.sType      = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
		create_info.magFilter  = VK_FILTER_NEAREST;
		create_info.minFilter  = VK_FILTER_NEAREST;
		create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;

		vk.CreateSampler(vk.device, &create_info, 0, &vk.sampler);
	}

	result = (vk.err == VK_SUCCESS);
	return result;
}

Vk_Frame *Vk_FrameBegin(SDL_Window *window) {
	(void) window; // might need this for the resize later
	Vk_Frame *frame = &vk.frames[vk.n_frames % vk.in_flight];

	vk.WaitForFences(vk.device, 1, &frame->fence, VK_TRUE, UINT64_MAX);

	vk.ResetFences(vk.device, 1, &frame->fence);
	vk.ResetCommandPool(vk.device, frame->pool, 0);
	vk.ResetDescriptorPool(vk.device, frame->descriptors, 0);

	VkResult res = vk.AcquireNextImageKHR(vk.device, vk.swapchain.handle, UINT64_MAX, frame->acquire, 0, &frame->image);
	(void) res; // @Todo: check res to see if swapchain is out of date and rebuild

	VkCommandBufferBeginInfo begin_info = { 0 };
	begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;

	vk.BeginCommandBuffer(frame->cmd, &begin_info);

	VkImageMemoryBarrier2 colour_optimal = { 0 };
	colour_optimal.sType         = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
	colour_optimal.srcStageMask  = VK_PIPELINE_STAGE_2_NONE;
	colour_optimal.srcAccessMask = VK_ACCESS_2_NONE;
	colour_optimal.dstStageMask  = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
	colour_optimal.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
	colour_optimal.oldLayout     = VK_IMAGE_LAYOUT_UNDEFINED;
	colour_optimal.newLayout     = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
	colour_optimal.image         = vk.swapchain.images[frame->image];

	colour_optimal.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
	colour_optimal.subresourceRange.layerCount = 1;
	colour_optimal.subresourceRange.levelCount = 1;

	VkDependencyInfo colour_barrier = { 0 };
	colour_barrier.sType                   = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
	colour_barrier.imageMemoryBarrierCount = 1;
	colour_barrier.pImageMemoryBarriers    = &colour_optimal;

	vk.CmdPipelineBarrier2(frame->cmd, &colour_barrier);

	return frame;
}

void Vk_FrameEnd() {
	Vk_Frame *frame = &vk.frames[vk.n_frames % vk.in_flight];

	VkImageMemoryBarrier2 present_src = { 0 };
	present_src.sType         = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
	present_src.srcStageMask  = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
	present_src.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
	present_src.dstStageMask  = VK_PIPELINE_STAGE_2_NONE;
	present_src.dstAccessMask = VK_ACCESS_2_NONE;
	present_src.oldLayout     = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
	present_src.newLayout     = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
	present_src.image         = vk.swapchain.images[frame->image];

	present_src.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
	present_src.subresourceRange.layerCount = 1;
	present_src.subresourceRange.levelCount = 1;

	VkDependencyInfo to_present = { 0 };
	to_present.sType                   = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
	to_present.imageMemoryBarrierCount = 1;
	to_present.pImageMemoryBarriers    = &present_src;

	vk.CmdPipelineBarrier2(frame->cmd, &to_present);

	vk.EndCommandBuffer(frame->cmd);

	VkPipelineStageFlags stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;

	VkSubmitInfo submit = { 0 };
	submit.sType                = VK_STRUCTURE_TYPE_SUBMIT_INFO;
	submit.waitSemaphoreCount   = 1;
	submit.pWaitSemaphores      = &frame->acquire;
	submit.pWaitDstStageMask    = &stage;
	submit.commandBufferCount   = 1;
	submit.pCommandBuffers      = &frame->cmd;
	submit.signalSemaphoreCount = 1;
	submit.pSignalSemaphores    = &frame->complete;

	vk.QueueSubmit(vk.queue.handle, 1, &submit, frame->fence);

	VkPresentInfoKHR present = { 0 };
	present.sType              = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
	present.waitSemaphoreCount = 1;
	present.pWaitSemaphores    = &frame->complete;
	present.swapchainCount     = 1;
	present.pSwapchains        = &vk.swapchain.handle;
	present.pImageIndices      = &frame->image;

	vk.QueuePresentKHR(vk.queue.handle, &present);

	vk.n_frames += 1;
}

Vk_CommandBuffer *Vk_CommandBufferPush() {
	Vk_Frame *frame = &vk.frames[vk.n_frames % vk.in_flight];

	// If this scratch buffer is still in use wait for it to finish, this is _bad_ but we
	// shouldn't hit this
	U32 idx = frame->next_scratch & (VK_NUM_SCRATCH - 1);

	Vk_CommandBuffer *result = &frame->scratch[idx];

	vk.WaitForFences(vk.device, 1, &result->fence, VK_TRUE, U64_MAX);
	vk.ResetFences(vk.device, 1, &result->fence);

	VkCommandBufferBeginInfo begin_info = { 0 };
	begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;

	vk.BeginCommandBuffer(result->handle, &begin_info);

	frame->next_scratch += 1;

	return result;
}

void Vk_CommandBufferSubmit(Vk_CommandBuffer *cmds, B32 wait) {
	vk.EndCommandBuffer(cmds->handle);

	VkSubmitInfo submit_info = { 0 };
	submit_info.sType                = VK_STRUCTURE_TYPE_SUBMIT_INFO;
	submit_info.commandBufferCount   = 1;
	submit_info.pCommandBuffers      = &cmds->handle;

	vk.QueueSubmit(vk.queue.handle, 1, &submit_info, cmds->fence);

	if (wait) { vk.DeviceWaitIdle(vk.device); }
}

#define VK_HOST_VISIBLE_FLAGS (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)

internal VkDeviceMemory Vk_Allocate(VkMemoryRequirements *mreq, VkMemoryPropertyFlags usage) {
	VkDeviceMemory result = VK_NULL_HANDLE;

	VkPhysicalDeviceMemoryProperties2 _props = { 0 };
	_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2;

	vk.GetPhysicalDeviceMemoryProperties2(vk.gpu, &_props);

	// ?????
	VkPhysicalDeviceMemoryProperties *props = &_props.memoryProperties;

	U32 type_index = U32_MAX;

	for (U32 it = 0; it < props->memoryTypeCount; ++it) {
		VkMemoryType *type = &props->memoryTypes[it];
		if ((type->propertyFlags & usage) == usage) {
			type_index = it;
			break;
		}
	}

	if (type_index != U32_MAX) {
		VkMemoryAllocateInfo alloc_info = { 0 };
		alloc_info.sType           = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
		alloc_info.allocationSize  = mreq->size;
		alloc_info.memoryTypeIndex = type_index;

		vk.AllocateMemory(vk.device, &alloc_info, 0, &result);
	}

	Assert(result != VK_NULL_HANDLE);
	return result;
}

void Vk_BufferCreate(Vk_Buffer *buffer) {
	VkBufferCreateInfo create_info = { 0 };
	create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
	create_info.usage = buffer->usage;
	create_info.size  = buffer->size;

	vk.CreateBuffer(vk.device, &create_info, 0, &buffer->handle);
	Assert(buffer->handle != VK_NULL_HANDLE);

	VkMemoryRequirements req;
	vk.GetBufferMemoryRequirements(vk.device, buffer->handle, &req);

	VkMemoryPropertyFlags usage = buffer->host_visible ? VK_HOST_VISIBLE_FLAGS : VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;

	buffer->size   = req.size;
	buffer->memory = Vk_Allocate(&req, usage);

	vk.BindBufferMemory(vk.device, buffer->handle, buffer->memory, 0);

	if (buffer->host_visible) {
		vk.MapMemory(vk.device, buffer->memory, 0, buffer->size, 0, &buffer->data);
	}

}

void Vk_ImageCreate(Vk_Image *image) {
	VkImageCreateInfo create_info = { 0 };
	create_info.sType         = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
	create_info.imageType     = VK_IMAGE_TYPE_2D;
	create_info.format        = image->format;
	create_info.extent.width  = image->width;
	create_info.extent.height = image->height;
	create_info.extent.depth  = 1;
	create_info.mipLevels     = 1;
	create_info.arrayLayers   = 1;
	create_info.usage         = VK_IMAGE_USAGE_TRANSFER_DST_BIT | image->usage;
	create_info.samples       = VK_SAMPLE_COUNT_1_BIT;
	create_info.tiling        = VK_IMAGE_TILING_OPTIMAL;

	vk.CreateImage(vk.device, &create_info, 0, &image->handle);

	VkMemoryRequirements req;
	vk.GetImageMemoryRequirements(vk.device, image->handle, &req);

	image->size   = req.size;
	image->memory = Vk_Allocate(&req, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

	vk.BindImageMemory(vk.device, image->handle, image->memory, 0);

	VkImageViewCreateInfo view_info = { 0 };
	view_info.sType    = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
	view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
	view_info.format   = image->format;
	view_info.image    = image->handle;

	view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
	view_info.subresourceRange.levelCount = 1;
	view_info.subresourceRange.layerCount = 1;

	vk.CreateImageView(vk.device, &view_info, 0, &image->view);
}

void Vk_PipelineCreate(Vk_Pipeline *pipeline) {
	VkPipelineInputAssemblyStateCreateInfo ia = { 0 };
	ia.sType    = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
	ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;

	VkPipelineShaderStageCreateInfo shaders[8] = { 0 };
	for (U32 it = 0; it < pipeline->shaders.count; ++it) {
		shaders[it].sType  = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
		shaders[it].stage  = pipeline->shaders.items[it].stage;
		shaders[it].module = pipeline->shaders.items[it].handle;
		shaders[it].pName  = "main";
	}

	VkPipelineVertexInputStateCreateInfo vi = { 0 };
	vi.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;

	// This will be updated later using dynamic commands but we still need to supply one here for
	// some reason
	VkViewport viewport = { 0, 0, 10, 10, 0, 1 };
	VkRect2D   scissor  = { 0, 0, 10, 10 };

	VkPipelineViewportStateCreateInfo vp = { 0 };
	vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
	vp.viewportCount = 1;
	vp.pViewports    = &viewport;
	vp.scissorCount  = 1;
	vp.pScissors     = &scissor;

	VkPipelineRasterizationStateCreateInfo rs = { 0 };
	rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
	rs.lineWidth = 1.0f;

	VkPipelineMultisampleStateCreateInfo ms = { 0 };
	ms.sType                = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
	ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;

	VkPipelineDepthStencilStateCreateInfo ds = { 0 };
	ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;

	VkPipelineColorBlendAttachmentState blend = { 0 };
	blend.blendEnable         = VK_TRUE;
	blend.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
	blend.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
	blend.colorBlendOp        = VK_BLEND_OP_ADD;
	blend.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
	blend.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
	blend.alphaBlendOp        = VK_BLEND_OP_ADD;
	blend.colorWriteMask      = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;

	VkPipelineColorBlendStateCreateInfo om = { 0 };
	om.sType           = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
	om.attachmentCount = 1;
	om.pAttachments    = &blend;

	VkDynamicState dynamic_state[] = {
		VK_DYNAMIC_STATE_VIEWPORT,
		VK_DYNAMIC_STATE_SCISSOR
	};

	VkPipelineDynamicStateCreateInfo dyn = { 0 };
	dyn.sType             = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
	dyn.dynamicStateCount = ArraySize(dynamic_state);
	dyn.pDynamicStates    = dynamic_state;

	VkFormat formats[8];
	for (U32 it = 0; it < pipeline->targets.count; ++it) {
		formats[it] = pipeline->targets.items[it];
	}

	VkPipelineRenderingCreateInfo rendering_info = { 0 };
	rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
	rendering_info.colorAttachmentCount    = pipeline->targets.count;
	rendering_info.pColorAttachmentFormats = formats;

	VkGraphicsPipelineCreateInfo create_info = { 0 };
	create_info.sType               = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
	create_info.pNext               = &rendering_info;
	create_info.stageCount          = pipeline->shaders.count;
	create_info.pStages             = shaders;
	create_info.pVertexInputState   = &vi;
	create_info.pInputAssemblyState = &ia;
	create_info.pViewportState      = &vp;
	create_info.pRasterizationState = &rs;
	create_info.pMultisampleState   = &ms;
	create_info.pDepthStencilState  = &ds;
	create_info.pColorBlendState    = &om;
	create_info.pDynamicState       = &dyn;
	create_info.layout              = pipeline->layout.pipeline;

	vk.CreateGraphicsPipelines(vk.device, 0, 1, &create_info, 0, &pipeline->handle);
}
