/*
 * Copyright © 2020 Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Author:
 *    Eleni Maria Stea <estea@igalia.com>
 *    Juan A. Suarez Romero <jasuarez@igalia.com>
 *    Topi Pohjolainen <topi.pohjolainen@intel.com>
 */

#include "vk.h"

#ifndef VK_NULL_HANDLE
#define VK_NULL_HANDLE 0
#endif

/* static variables */
static VkViewport viewport;
static VkRect2D scissor;

/* static functions */
static VkSampleCountFlagBits
get_num_samples(uint32_t num_samples);

/* Vulkan static functions */
static VkAccessFlagBits
get_access_mask(const VkImageLayout layout)
{
	/* dstAccessMask of barriers must be supported from the pipeline
	 * stage, see also access scopes and this table:
	 * https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#synchronization-access-types-supported
	 */
	switch (layout) {
	case VK_IMAGE_LAYOUT_UNDEFINED:
		return 0;
	case VK_IMAGE_LAYOUT_GENERAL:
		return VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
	case VK_IMAGE_LAYOUT_PREINITIALIZED:
		return VK_ACCESS_HOST_WRITE_BIT;
	case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
		return VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
		       VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
	case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
		return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
	case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
		return VK_ACCESS_TRANSFER_READ_BIT;
	case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
		return VK_ACCESS_TRANSFER_WRITE_BIT;
	case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
		return 0;
	default:
		return 0;
	};

	return 0;
}

static void
enable_validation_layers(VkInstanceCreateInfo *info)
{
	int i;
	uint32_t num_layers;
	VkLayerProperties *layers;
	static const char *layer_names[] = {
		"VK_LAYER_KHRONOS_validation",
	};

	vkEnumerateInstanceLayerProperties(&num_layers, 0);
	layers = alloca(num_layers * sizeof *layers);
	vkEnumerateInstanceLayerProperties(&num_layers, layers);

	if (num_layers) {
		printf("Available validation layers:\n");
		for(i = 0; i < (int)num_layers; i++) {
			printf(" %s\n", layers[i].layerName);
		}

		info->ppEnabledLayerNames = layer_names;
		info->enabledLayerCount = sizeof layer_names / sizeof *layer_names;
	} else {
		fprintf(stderr, "Vulkan validation layers not found.\n");
	}
}

static VkInstance
create_instance(bool enable_layers)
{
	VkApplicationInfo app_info;
	VkInstanceCreateInfo inst_info;
	VkInstance inst;

	memset(&app_info, 0, sizeof app_info);
	app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
	app_info.pApplicationName = "vktest";
	app_info.apiVersion = VK_API_VERSION_1_1;

	memset(&inst_info, 0, sizeof inst_info);
	inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
	inst_info.pApplicationInfo = &app_info;

	if (enable_layers)
		enable_validation_layers(&inst_info);

	if (vkCreateInstance(&inst_info, 0, &inst) != VK_SUCCESS)
		return VK_NULL_HANDLE;

	return inst;
}

static VkPhysicalDevice
select_physical_device(VkInstance inst)
{
	VkResult res = VK_SUCCESS;
	uint32_t dev_count = 0;
	VkPhysicalDevice *pdevices;
	VkPhysicalDevice pdevice0;

	if ((res =
	     vkEnumeratePhysicalDevices(inst, &dev_count, 0)) != VK_SUCCESS)
		return VK_NULL_HANDLE;

	pdevices = malloc(dev_count * sizeof(VkPhysicalDevice));
	if (vkEnumeratePhysicalDevices(inst, &dev_count, pdevices) !=
	    VK_SUCCESS)
		return VK_NULL_HANDLE;

	pdevice0 = pdevices[0];
	free(pdevices);

	return pdevice0;
}

static VkDevice
create_device(struct vk_ctx *ctx, VkPhysicalDevice pdev)
{
	const char *required_exts[] = {
		"VK_KHR_external_memory_fd",
		"VK_KHR_external_semaphore_fd",
	};
	const char *optional_exts[] = {
		"VK_KHR_timeline_semaphore",
	};
	VkDeviceQueueCreateInfo dev_queue_info;
	VkDeviceCreateInfo dev_info;
	VkDevice dev;
	uint32_t prop_count;
	VkQueueFamilyProperties *fam_props;
	uint32_t i;
	float qprio = 0;

	VkExtensionProperties *ext_props;
	uint32_t n_ext_props = 0;
	vkEnumerateDeviceExtensionProperties(pdev, NULL, &n_ext_props, NULL);
	ext_props = calloc(n_ext_props, sizeof *ext_props);
	vkEnumerateDeviceExtensionProperties(pdev, NULL, &n_ext_props, ext_props);

	const char *exts[ARRAY_SIZE(required_exts) + ARRAY_SIZE(optional_exts)];
	uint32_t n_exts = 0;

	/* Unconditionally add these. Device create will fail if one of them
	 * isn't supported.
	 */
	for (uint32_t i = 0; i < ARRAY_SIZE(required_exts); i++)
		exts[n_exts++] = required_exts[i];

	for (uint32_t i = 0; i < n_ext_props; i++) {
		for (uint32_t j = 0; j < ARRAY_SIZE(optional_exts); j++) {
			if (!strcmp(ext_props[i].extensionName, optional_exts[j]))
				exts[n_exts++] = optional_exts[j];
		}
	}

	free(ext_props);

	/* Stash them in the context for extension support checks */
	ctx->dev_exts = calloc(n_exts, sizeof *ctx->dev_exts);
	for (uint32_t i = 0; i < n_exts; i++)
		ctx->dev_exts[i] = exts[i];
	ctx->n_dev_exts = n_exts;

	ctx->qfam_idx = -1;
	vkGetPhysicalDeviceQueueFamilyProperties(pdev, &prop_count, 0);

	fam_props = malloc(prop_count * sizeof *fam_props);
	vkGetPhysicalDeviceQueueFamilyProperties(pdev, &prop_count, fam_props);

	for (i = 0; i < prop_count; i++) {
		if (fam_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
			ctx->qfam_idx = i;
			break;
		}
	}
	free(fam_props);

	memset(&dev_queue_info, 0, sizeof dev_queue_info);
	dev_queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
	dev_queue_info.queueFamilyIndex = ctx->qfam_idx;
	dev_queue_info.queueCount = 1;
	dev_queue_info.pQueuePriorities = &qprio;

	struct VkPhysicalDeviceTimelineSemaphoreFeatures timeline_semaphore_feature = {
		.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES,
		.timelineSemaphore = VK_TRUE,
	};

	memset(&dev_info, 0, sizeof dev_info);
	dev_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
	dev_info.queueCreateInfoCount = 1;
	dev_info.pQueueCreateInfos = &dev_queue_info;
	dev_info.enabledExtensionCount = n_exts;
	dev_info.ppEnabledExtensionNames = exts;

	if (vk_device_extension_supported(ctx, "VK_KHR_timeline_semaphore"))
		dev_info.pNext = &timeline_semaphore_feature;

	if (vkCreateDevice(pdev, &dev_info, 0, &dev) != VK_SUCCESS)
		return VK_NULL_HANDLE;

	return dev;
}

static void
fill_uuid(VkPhysicalDevice pdev, uint8_t *deviceUUID, uint8_t *driverUUID)
{
	VkPhysicalDeviceIDProperties devProp;
	VkPhysicalDeviceProperties2 prop2;

	memset(&devProp, 0, sizeof devProp);
	devProp.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES;

	memset(&prop2, 0, sizeof prop2);
	prop2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
	prop2.pNext = &devProp;

	vkGetPhysicalDeviceProperties2(pdev, &prop2);
	memcpy(deviceUUID, devProp.deviceUUID, VK_UUID_SIZE);
	memcpy(driverUUID, devProp.driverUUID, VK_UUID_SIZE);
}

static VkCommandPool
create_cmd_pool(struct vk_ctx *ctx)
{
	VkCommandPoolCreateInfo cmd_pool_info;
	VkCommandPool cmd_pool;
	VkDevice dev = ctx->dev;

	memset(&cmd_pool_info, 0, sizeof cmd_pool_info);
	cmd_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
	cmd_pool_info.queueFamilyIndex = ctx->qfam_idx;
	cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;

	if (vkCreateCommandPool(dev, &cmd_pool_info, 0, &cmd_pool) != VK_SUCCESS)
		return VK_NULL_HANDLE;

	return cmd_pool;
}

static VkRenderPass
create_renderpass(struct vk_ctx *ctx,
		  struct vk_image_props *color_img_props,
		  struct vk_image_props *depth_img_props)
{
	uint32_t num_attachments = depth_img_props ? 2 : 1;
	VkAttachmentDescription att_dsc[2];
	VkAttachmentReference att_rfc[2];
	VkSubpassDescription subpass_dsc[1];
	VkRenderPassCreateInfo rpass_info;

	/* VkAttachmentDescription */
	memset(att_dsc, 0, num_attachments * sizeof att_dsc[0]);

	att_dsc[0].samples = get_num_samples(color_img_props->num_samples);
	att_dsc[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
	att_dsc[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
	att_dsc[0].initialLayout = color_img_props->in_layout;
	att_dsc[0].finalLayout = color_img_props->end_layout;
	att_dsc[0].format = color_img_props->format;
	att_dsc[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
	att_dsc[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;

	if (depth_img_props) {

		att_dsc[1].samples = get_num_samples(depth_img_props->num_samples);
		/* We might want to reuse a depth buffer */
		if (depth_img_props->in_layout != VK_IMAGE_LAYOUT_UNDEFINED) {
			att_dsc[1].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
			att_dsc[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
		}
		else {
			att_dsc[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
			att_dsc[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
		}
		att_dsc[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
		att_dsc[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
		att_dsc[1].initialLayout = depth_img_props->in_layout;
		att_dsc[1].finalLayout = depth_img_props->end_layout;
		att_dsc[1].format = depth_img_props->format;
	}

	/* VkAttachmentReference */
	memset(att_rfc, 0, num_attachments * sizeof att_rfc[0]);

	att_rfc[0].layout = color_img_props->tiling == VK_IMAGE_TILING_OPTIMAL ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_GENERAL;
	att_rfc[0].attachment = 0;

	if (depth_img_props) {
		att_rfc[1].layout = depth_img_props->tiling == VK_IMAGE_TILING_OPTIMAL ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_GENERAL;
		att_rfc[1].attachment = 1;
	}

	/* VkSubpassDescription */
	memset(&subpass_dsc, 0, sizeof subpass_dsc);
	subpass_dsc[0].pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
	subpass_dsc[0].colorAttachmentCount = 1;
	subpass_dsc[0].pColorAttachments = &att_rfc[0];

	if (depth_img_props) {
		subpass_dsc[0].pDepthStencilAttachment = &att_rfc[1];
	}

	/* VkRenderPassCreateInfo */
	memset(&rpass_info, 0, sizeof rpass_info);
	rpass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
	rpass_info.attachmentCount = num_attachments;
	rpass_info.pAttachments = att_dsc;
	rpass_info.subpassCount = 1;
	rpass_info.pSubpasses = subpass_dsc;

	VkRenderPass rpass;
	if (vkCreateRenderPass(ctx->dev, &rpass_info, 0, &rpass) != VK_SUCCESS) {
		fprintf(stderr, "Failed to create renderpass.\n");
		rpass = VK_NULL_HANDLE;
	}

	return rpass;
}

static inline VkImageType
get_image_type(uint32_t h, uint32_t d)
{
	if (h == 1)
		return VK_IMAGE_TYPE_1D;

	if (d > 1)
		return VK_IMAGE_TYPE_3D;

	return VK_IMAGE_TYPE_2D;
}

static VkImageViewType
get_image_view_type(struct vk_image_props *props)
{
	VkImageType type = get_image_type(props->h, props->depth);
	switch(type) {
		case VK_IMAGE_TYPE_1D:
			return props->num_layers > 1 ?
				VK_IMAGE_VIEW_TYPE_1D_ARRAY :
				VK_IMAGE_VIEW_TYPE_1D;
		case VK_IMAGE_TYPE_2D:
			if (props->num_layers == 1)
				return VK_IMAGE_VIEW_TYPE_2D;
			if (props->num_layers == 6)
				return VK_IMAGE_VIEW_TYPE_CUBE;
			if (props->num_layers % 6 == 0)
				return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
			if (props->num_layers > 1)
				return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
		case VK_IMAGE_TYPE_3D:
			if (props->num_layers == 1)
				return VK_IMAGE_VIEW_TYPE_3D;
			if ((props->num_layers == 1) &&
			    (props->num_levels == 1))
				return VK_IMAGE_VIEW_TYPE_2D;
			if ((props->num_levels == 1) &&
			    (props->num_layers > 1))
				return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
		default:
			return VK_IMAGE_VIEW_TYPE_2D;
	}
}

static VkImageAspectFlagBits
get_aspect_from_depth_format(VkFormat depth_format)
{
	switch (depth_format) {
	case VK_FORMAT_D16_UNORM:
	case VK_FORMAT_X8_D24_UNORM_PACK32:
	case VK_FORMAT_D32_SFLOAT:
		return VK_IMAGE_ASPECT_DEPTH_BIT;
	case VK_FORMAT_S8_UINT:
		return VK_IMAGE_ASPECT_STENCIL_BIT;
	case VK_FORMAT_D16_UNORM_S8_UINT:
	case VK_FORMAT_D24_UNORM_S8_UINT:
	case VK_FORMAT_D32_SFLOAT_S8_UINT:
		return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
	default:
		break;
	}
	return 0;
}

static VkPipelineStageFlags
get_pipeline_stage_flags(const VkImageLayout layout)
{
	switch (layout) {
	case VK_IMAGE_LAYOUT_UNDEFINED:
		return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
	case VK_IMAGE_LAYOUT_GENERAL:
		return VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
	case VK_IMAGE_LAYOUT_PREINITIALIZED:
		return VK_PIPELINE_STAGE_HOST_BIT;
	case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
	case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
		return VK_PIPELINE_STAGE_TRANSFER_BIT;
	case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
		return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
	case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
		return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
		       VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
	case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
		return VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
	default:
		return 0;
	}
	return 0;
}

static void
create_framebuffer(struct vk_ctx *ctx,
		   struct vk_image_att *color_att,
		   struct vk_image_att *depth_att,
		   struct vk_renderer *renderer)
{
	VkImageSubresourceRange sr;
	VkImageViewCreateInfo color_info;
	VkImageViewCreateInfo depth_info;
	VkFramebufferCreateInfo fb_info;
	VkImageViewType view_type = get_image_view_type(&color_att->props);
	VkImageView atts[2];

	if (!color_att->obj.img || (depth_att && !depth_att->obj.img)) {
		fprintf(stderr, "Invalid framebuffer attachment image.\n");
		goto fail;
	}

	/* create image views */

	/* VKImageSubresourceRange */
	memset(&sr, 0, sizeof sr);
	sr.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
	/* If an application wants to use all mip levels
	 * or layers in an image after the baseMipLevel
	 * or baseArrayLayer, it can set levelCount and
	 * layerCount to the special values
	 * VK_REMAINING_MIP_LEVELS and
	 * VK_REMAINING_ARRAY_LAYERS without knowing the
	 * exact number of mip levels or layers.
	 */
	sr.baseMipLevel = 0;
	sr.levelCount = color_att->props.num_levels;
	sr.baseArrayLayer = 0;
	sr.layerCount = color_att->props.num_layers;

	/* color view */
	memset(&color_info, 0, sizeof color_info);
	color_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
	color_info.image = color_att->obj.img;
	color_info.viewType = view_type;
	color_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
	color_info.format = color_att->props.format;
	color_info.subresourceRange = sr;

	if (vkCreateImageView(ctx->dev, &color_info, 0, &color_att->obj.img_view) != VK_SUCCESS) {
		fprintf(stderr, "Failed to create color image view for framebuffer.\n");
		vk_destroy_ext_image(ctx, &color_att->obj);
		goto fail;
	}

	if (depth_att) {
		/* depth view */
		memset(&sr, 0, sizeof sr);
		sr.aspectMask = get_aspect_from_depth_format(depth_att->props.format);
		sr.baseMipLevel = 0;
		sr.levelCount = depth_att->props.num_levels ? depth_att->props.num_levels : 1;
		sr.baseArrayLayer = 0;
		sr.layerCount = depth_att->props.num_layers ? depth_att->props.num_layers : 1;

		memset(&depth_info, 0, sizeof depth_info);
		depth_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
		depth_info.image = depth_att->obj.img;
		depth_info.viewType = depth_att->props.num_layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D;
		depth_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
		depth_info.format = depth_att->props.format;
		depth_info.subresourceRange = sr;

		if (vkCreateImageView(ctx->dev, &depth_info, 0, &depth_att->obj.img_view) != VK_SUCCESS) {
			fprintf(stderr, "Failed to create depth image view for framebuffer.\n");
			vk_destroy_ext_image(ctx, &depth_att->obj);
			goto fail;
		}
	}

	atts[0] = color_att->obj.img_view;

	if (depth_att)
		atts[1] = depth_att->obj.img_view;

	memset(&fb_info, 0, sizeof fb_info);
	fb_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
	fb_info.renderPass = renderer->renderpass;
	fb_info.width = color_att->props.w;
	fb_info.height = color_att->props.h;
	fb_info.layers = color_att->props.num_layers ? color_att->props.num_layers : 1;
	fb_info.attachmentCount = depth_att ? 2 : 1;
	fb_info.pAttachments = atts;

	if (vkCreateFramebuffer(ctx->dev, &fb_info, 0, &renderer->fb) != VK_SUCCESS)
		goto fail;

	return;

fail:
	fprintf(stderr, "Failed to create framebuffer.\n");
	renderer->fb = VK_NULL_HANDLE;
}

static VkShaderModule
create_shader_module(struct vk_ctx *ctx,
		     const char *src,
		     unsigned int size)
{
	VkShaderModuleCreateInfo sm_info;
	VkShaderModule sm;

	/* VkShaderModuleCreateInfo */
	memset(&sm_info, 0, sizeof sm_info);
	sm_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
	sm_info.codeSize = size;
	sm_info.pCode = (void*)src;

	if (vkCreateShaderModule(ctx->dev, &sm_info, 0, &sm) != VK_SUCCESS) {
		fprintf(stderr, "Failed to create shader module.\n");
		sm = VK_NULL_HANDLE;
	}

	return sm;
}

static void
create_pipeline(struct vk_ctx *ctx,
		uint32_t width,
		uint32_t height,
		uint32_t num_samples,
		bool enable_depth,
		bool enable_stencil,
		struct vk_renderer *renderer)
{
	VkVertexInputBindingDescription vert_bind_dsc[1];
	VkVertexInputAttributeDescription vert_att_dsc[1];

	VkPipelineColorBlendAttachmentState cb_att_state[1];
	VkPipelineVertexInputStateCreateInfo vert_input_info;
	VkPipelineInputAssemblyStateCreateInfo asm_info;
	VkPipelineViewportStateCreateInfo viewport_info;
	VkPipelineRasterizationStateCreateInfo rs_info;
	VkPipelineMultisampleStateCreateInfo ms_info;
	VkPipelineDepthStencilStateCreateInfo ds_info;
	VkPipelineColorBlendStateCreateInfo cb_info;
	VkPipelineShaderStageCreateInfo sdr_stages[2];
	VkPipelineLayoutCreateInfo layout_info;
	VkGraphicsPipelineCreateInfo pipeline_info;
	VkFormat format;
	VkFormatProperties fmt_props;
	VkPushConstantRange pc_range[1];

	VkStencilOpState front;
	VkStencilOpState back;
	int i;
	VkPipelineLayout pipeline_layout;
	uint32_t stride;

	/* format of vertex attributes:
	 * we have 2D vectors so we need a RG format:
	 * R for x, G for y
	 * the stride (distance between 2 consecutive elements)
	 * must be 8 because we use 32 bit floats and
	 * 32bits = 8bytes */
	format = VK_FORMAT_R32G32_SFLOAT;
	vkGetPhysicalDeviceFormatProperties(ctx->pdev, format, &fmt_props);
	assert(fmt_props.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
	stride = 8;

	/* VkVertexInputAttributeDescription */
	memset(&vert_att_dsc, 0, sizeof vert_att_dsc);
	vert_att_dsc[0].location = 0;
	vert_att_dsc[0].binding = 0;
	vert_att_dsc[0].format = format; /* see comment */
	vert_att_dsc[0].offset = 0;

	/* VkVertexInputBindingDescription */
	memset(&vert_bind_dsc, 0, sizeof vert_bind_dsc);
	vert_bind_dsc[0].binding = 0;
	vert_bind_dsc[0].stride = stride;
	vert_bind_dsc[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

	/* If using vbo, we have setup vertex_info in the renderer. */
	bool use_vbo = renderer->vertex_info.num_verts > 0;

	/* VkPipelineVertexInputStateCreateInfo */
	memset(&vert_input_info, 0, sizeof vert_input_info);
	vert_input_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
	vert_input_info.vertexBindingDescriptionCount = use_vbo ? 1 : 0;
	vert_input_info.pVertexBindingDescriptions = vert_bind_dsc;
	vert_input_info.vertexAttributeDescriptionCount = use_vbo ? 1 : 0;
	vert_input_info.pVertexAttributeDescriptions = vert_att_dsc;

	/* VkPipelineInputAssemblyStateCreateInfo */
	memset(&asm_info, 0, sizeof asm_info);
	asm_info.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
	asm_info.topology = renderer->vertex_info.topology ?
			    renderer->vertex_info.topology : VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
	asm_info.primitiveRestartEnable = false;

	/* VkViewport */
	viewport.x = viewport.y = 0;
	viewport.width = width;
	viewport.height = height;
	viewport.minDepth = 0;
	viewport.maxDepth = 1;

	/* VkRect2D scissor */
	scissor.offset.x = scissor.offset.y = 0;
	scissor.extent.width = width;
	scissor.extent.height = height;

	/* VkPipelineViewportStateCreateInfo */
	memset(&viewport_info, 0, sizeof viewport_info);
	viewport_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
	viewport_info.viewportCount = 1;
	viewport_info.pViewports = &viewport;
	viewport_info.scissorCount = 1;
	viewport_info.pScissors = &scissor;

	/* VkPipelineRasterizationStateCreateInfo */
	memset(&rs_info, 0, sizeof rs_info);
	rs_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
	rs_info.polygonMode = VK_POLYGON_MODE_FILL;
	rs_info.cullMode = VK_CULL_MODE_NONE;
	rs_info.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
	rs_info.lineWidth = 1.0;

	/* VkPipelineMultisampleStateCreateInfo */
	memset(&ms_info, 0, sizeof ms_info);
	ms_info.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
	ms_info.rasterizationSamples = num_samples;

	/* VkStencilOpState */
	/* The default values for ES are taken by Topi Pohjolainen's code */
	/* defaults in OpenGL ES 3.1 */
	memset(&front, 0, sizeof front);
	front.compareMask = ~0;
	front.writeMask = ~0;
	front.reference = 0;

	memset(&back, 0, sizeof back);

	/* VkPipelineDepthStencilStateCreateInfo */
	memset(&ds_info, 0, sizeof ds_info);
	ds_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
	ds_info.front = front;
	ds_info.back = back;
	/* defaults in OpenGL ES 3.1 */
	ds_info.minDepthBounds = 0;
	ds_info.maxDepthBounds = 1;
	/* z buffer, stencil buffer */
	if (enable_depth) {
		ds_info.depthTestEnable = VK_TRUE;
		ds_info.depthWriteEnable = VK_TRUE;
		ds_info.depthCompareOp = VK_COMPARE_OP_LESS;
	}
	if (enable_stencil) {
		ds_info.stencilTestEnable = VK_TRUE;
		ds_info.depthTestEnable = VK_FALSE;
		ds_info.depthWriteEnable = VK_TRUE;
	}

	/* we only care about the passOp here */
	ds_info.back.compareOp = VK_COMPARE_OP_ALWAYS;
	ds_info.back.failOp = VK_STENCIL_OP_REPLACE;
	ds_info.back.depthFailOp = VK_STENCIL_OP_REPLACE;
	ds_info.back.passOp = VK_STENCIL_OP_REPLACE;
	ds_info.back.compareMask = 0xffffffff;
	ds_info.back.writeMask = 0xffffffff;
	ds_info.back.reference = 1;
	ds_info.front = ds_info.back;

	/* VkPipelineColorBlendAttachmentState */
	memset(&cb_att_state[0], 0, sizeof cb_att_state[0]);
	cb_att_state[0].colorWriteMask = (VK_COLOR_COMPONENT_R_BIT |
					  VK_COLOR_COMPONENT_G_BIT |
					  VK_COLOR_COMPONENT_B_BIT |
					  VK_COLOR_COMPONENT_A_BIT);

	/* VkPipelineColorBlendStateCreateInfo */
	memset(&cb_info, 0, sizeof cb_info);
	cb_info.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
	cb_info.attachmentCount = 1;
	cb_info.pAttachments = cb_att_state;
	/* default in ES 3.1 */
	for (i = 0; i < 4; i++) {
		cb_info.blendConstants[i] = 0.0f;
	}

	/* VkPipelineShaderStageCreateInfo */
	memset(sdr_stages, 0, 2 * sizeof sdr_stages[0]);

	sdr_stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
	sdr_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
	sdr_stages[0].module = renderer->vs;
	sdr_stages[0].pName = "main";

	sdr_stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
	sdr_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
	sdr_stages[1].module = renderer->fs;
	sdr_stages[1].pName = "main";

	/* VkPushConstantRange */
	memset(pc_range, 0, sizeof pc_range[0]);
	pc_range[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
	pc_range[0].size = sizeof (struct vk_dims); /* w, h */

	/* VkPipelineLayoutCreateInfo */
	memset(&layout_info, 0, sizeof layout_info);
	layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
	layout_info.pushConstantRangeCount = 1;
	layout_info.pPushConstantRanges = pc_range;

	if (vkCreatePipelineLayout(ctx->dev, &layout_info, 0, &pipeline_layout) != VK_SUCCESS) {
		fprintf(stderr, "Failed to create pipeline layout\n");
		renderer->pipeline = VK_NULL_HANDLE;
		return;
	}

	renderer->pipeline_layout = pipeline_layout;

	/* VkGraphicsPipelineCreateInfo */
	memset(&pipeline_info, 0, sizeof pipeline_info);
	pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
	pipeline_info.layout = pipeline_layout;
	pipeline_info.renderPass = renderer->renderpass;
	pipeline_info.pVertexInputState = &vert_input_info;
	pipeline_info.pInputAssemblyState = &asm_info;
	pipeline_info.pViewportState = &viewport_info;
	pipeline_info.pRasterizationState = &rs_info;
	pipeline_info.pMultisampleState = &ms_info;
	pipeline_info.pDepthStencilState = &ds_info;
	pipeline_info.pColorBlendState = &cb_info;
	pipeline_info.stageCount = 2;
	pipeline_info.pStages = sdr_stages;

	if (vkCreateGraphicsPipelines(ctx->dev, 0, 1,
				      &pipeline_info, 0, &renderer->pipeline) !=
			VK_SUCCESS) {
		fprintf(stderr, "Failed to create graphics pipeline.\n");
		renderer->pipeline = VK_NULL_HANDLE;
	}
}

static VkCommandBuffer
create_cmd_buf(VkDevice dev, VkCommandPool cmd_pool)
{
	VkCommandBuffer cmd_buf;
	VkCommandBufferAllocateInfo alloc_info;

	memset(&alloc_info, 0, sizeof alloc_info);
	alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
	alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
	alloc_info.commandBufferCount = 1;
	alloc_info.commandPool = cmd_pool;

	if (vkAllocateCommandBuffers(dev, &alloc_info, &cmd_buf) != VK_SUCCESS)
		return VK_NULL_HANDLE;

	return cmd_buf;
}

static uint32_t
get_memory_type_idx(VkPhysicalDevice pdev,
		    const VkMemoryRequirements *mem_reqs,
		    VkMemoryPropertyFlagBits prop_flags)
{
	VkPhysicalDeviceMemoryProperties pdev_mem_props;
	uint32_t i;

	vkGetPhysicalDeviceMemoryProperties(pdev, &pdev_mem_props);

	for (i = 0; i < pdev_mem_props.memoryTypeCount; i++) {
		const VkMemoryType *type = &pdev_mem_props.memoryTypes[i];

		if ((mem_reqs->memoryTypeBits & (1 << i)) &&
		    (type->propertyFlags & prop_flags) == prop_flags) {
			return i;
			break;
		}
	}
	return UINT32_MAX;
}

static VkSampleCountFlagBits
get_num_samples(uint32_t num_samples)
{
	switch(num_samples) {
	case 64:
		return VK_SAMPLE_COUNT_64_BIT;
	case 32:
		return VK_SAMPLE_COUNT_32_BIT;
	case 16:
		return VK_SAMPLE_COUNT_16_BIT;
	case 8:
		return VK_SAMPLE_COUNT_8_BIT;
	case 4:
		return VK_SAMPLE_COUNT_4_BIT;
	case 2:
		return VK_SAMPLE_COUNT_2_BIT;
	case 1:
		break;
	default:
		fprintf(stderr, "Invalid number of samples in VkSampleCountFlagBits. Using one sample.\n");
		break;
	}
	return VK_SAMPLE_COUNT_1_BIT;
}

static VkDeviceMemory
alloc_memory(struct vk_ctx *ctx,
	     bool is_external,
	     const VkMemoryRequirements *mem_reqs,
	     VkImage image,
	     VkBuffer buffer,
	     VkMemoryPropertyFlagBits prop_flags)
{
	VkExportMemoryAllocateInfo exp_mem_info;
	VkMemoryAllocateInfo mem_alloc_info;
	VkDeviceMemory mem;
	VkMemoryDedicatedAllocateInfoKHR ded_info;

	if (is_external) {
		memset(&exp_mem_info, 0, sizeof exp_mem_info);
		exp_mem_info.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
		exp_mem_info.handleTypes =
			VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
	}

	memset(&mem_alloc_info, 0, sizeof mem_alloc_info);
	mem_alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
	mem_alloc_info.pNext = is_external ? &exp_mem_info : 0;
	mem_alloc_info.allocationSize = mem_reqs->size;
	mem_alloc_info.memoryTypeIndex =
		get_memory_type_idx(ctx->pdev, mem_reqs, prop_flags);

	if (mem_alloc_info.memoryTypeIndex == UINT32_MAX) {
		fprintf(stderr, "No suitable memory type index found.\n");
		return VK_NULL_HANDLE;
	}

	if (image || buffer) {
		memset(&ded_info, 0, sizeof ded_info);
		ded_info.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
		ded_info.image = image;
		ded_info.buffer = buffer;

		exp_mem_info.pNext = &ded_info;
	}

	if (vkAllocateMemory(ctx->dev, &mem_alloc_info, 0, &mem) !=
	    VK_SUCCESS)
		return VK_NULL_HANDLE;

	return mem;
}

static bool
alloc_image_memory(struct vk_ctx *ctx, struct vk_image_obj *img_obj)
{
	VkMemoryDedicatedRequirements ded_reqs;
	VkImageMemoryRequirementsInfo2 req_info2;
	VkMemoryRequirements2 mem_reqs2;

	memset(&ded_reqs, 0, sizeof ded_reqs);
	ded_reqs.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS;

	/* VkImageMemoryRequirementsInfo2 */
	memset(&req_info2, 0, sizeof req_info2);
	req_info2.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2;
	req_info2.image = img_obj->img;

	/* VkMemoryRequirements2 */
	memset(&mem_reqs2, 0, sizeof mem_reqs2);
	mem_reqs2.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2;
	mem_reqs2.pNext = &ded_reqs;

	vkGetImageMemoryRequirements2(ctx->dev, &req_info2, &mem_reqs2);
	img_obj->mobj.mem = alloc_memory(ctx,
					 true,
					 &mem_reqs2.memoryRequirements,
					 ded_reqs.requiresDedicatedAllocation? img_obj->img : VK_NULL_HANDLE,
					 VK_NULL_HANDLE,
					 mem_reqs2.memoryRequirements.memoryTypeBits &
					 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

	img_obj->mobj.mem_sz = mem_reqs2.memoryRequirements.size;
	img_obj->mobj.dedicated = ded_reqs.requiresDedicatedAllocation;
	if (img_obj->mobj.mem == VK_NULL_HANDLE) {
		fprintf(stderr, "Failed to allocate image memory.\n");
		return false;
	}

	if (vkBindImageMemory(ctx->dev, img_obj->img, img_obj->mobj.mem, 0) != VK_SUCCESS) {
		fprintf(stderr, "Failed to bind image memory.\n");
		return false;
	}

	return true;
}

static bool
are_props_supported(struct vk_ctx *ctx, struct vk_image_props *props)
{
	VkPhysicalDeviceExternalImageFormatInfo ext_img_fmt_info;
	VkExternalImageFormatProperties ext_img_fmt_props;

	int i;
	VkPhysicalDeviceImageFormatInfo2 img_fmt_info;
	VkImageFormatProperties2 img_fmt_props;
	VkImageUsageFlagBits all_flags[] = {
		VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
		VK_IMAGE_USAGE_TRANSFER_DST_BIT,
		VK_IMAGE_USAGE_SAMPLED_BIT,
		VK_IMAGE_USAGE_STORAGE_BIT,
		VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
		VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
		VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT,
		/* Shouldn't be used together with COLOR, DEPTH_STENCIL
		 * attachment bits:
		 * VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT,
		 * Provided by VK_EXT_fragment_density_map
		 * VK_IMAGE_USAGE_FRAGMENT_DENSITY_MAP_BIT_EXT,
		 * Provided by VK_NV_shading_rate_image
		 * VK_IMAGE_USAGE_SHADING_RATE_IMAGE_BIT_NV,
		 * Provided by VK_KHR_fragment_shading_rate
		 * VK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR,
		 */
	};
	VkImageUsageFlagBits flags = 0;

	VkExternalMemoryFeatureFlagBits export_feature_flags =
		VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT;
	VkExternalMemoryHandleTypeFlagBits handle_type =
		VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;

	memset(&ext_img_fmt_info, 0, sizeof ext_img_fmt_info);
	ext_img_fmt_info.sType =
		VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO;
	ext_img_fmt_info.handleType = handle_type;

	memset(&ext_img_fmt_props, 0, sizeof ext_img_fmt_props);
	ext_img_fmt_props.sType =
		VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES;

	memset(&img_fmt_props, 0, sizeof img_fmt_props);
	img_fmt_props.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2;
	img_fmt_props.pNext = &ext_img_fmt_props;

	memset(&img_fmt_info, 0, sizeof img_fmt_info);
	img_fmt_info.sType =
		VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2;
	img_fmt_info.pNext = &ext_img_fmt_info;
	img_fmt_info.format = props->format;
	img_fmt_info.type = get_image_type(props->h, props->depth);
	img_fmt_info.tiling = props->tiling;

	for (i = 0; i < ARRAY_SIZE(all_flags); i++) {
		img_fmt_info.usage = all_flags[i];
		if (vkGetPhysicalDeviceImageFormatProperties2(ctx->pdev,
					&img_fmt_info,
					&img_fmt_props) == VK_SUCCESS) {
			flags |= all_flags[i];
		}
	}

	/* usage can't be null */
	if (flags) {
		img_fmt_info.usage = flags;
	}
	else {
		fprintf(stderr, "Unsupported Vulkan format properties: usage.\n");
		return false;
	}

	if (vkGetPhysicalDeviceImageFormatProperties2
	    (ctx->pdev, &img_fmt_info, &img_fmt_props) != VK_SUCCESS) {
		fprintf(stderr,
			"Unsupported Vulkan format properties.\n");
		return false;
	}
	props->usage = flags;

	if (props->need_export &&
	    !(ext_img_fmt_props.externalMemoryProperties.externalMemoryFeatures
		    & export_feature_flags)) {
		fprintf(stderr, "Unsupported Vulkan external memory features.\n");
		return false;
	}

	return true;
}

/* Vulkan test visible functions */

bool
vk_init_ctx(struct vk_ctx *ctx)
{
	if ((ctx->inst = create_instance(false)) == VK_NULL_HANDLE) {
		fprintf(stderr, "Failed to create Vulkan instance.\n");
		goto fail;
	}

	if ((ctx->pdev = select_physical_device(ctx->inst)) == VK_NULL_HANDLE) {
		fprintf(stderr, "Failed to find suitable physical device.\n");
		goto fail;
	}

	if ((ctx->dev = create_device(ctx, ctx->pdev)) == VK_NULL_HANDLE) {
		fprintf(stderr, "Failed to create Vulkan device.\n");
		goto fail;
	}

	fill_uuid(ctx->pdev, ctx->deviceUUID, ctx->driverUUID);
	return true;

fail:
	vk_cleanup_ctx(ctx);
	return false;
}

bool
vk_device_extension_supported(struct vk_ctx *ctx, const char *ext_name)
{
	for (uint32_t i = 0; i < ctx->n_dev_exts; i++) {
		if (!strcmp(ctx->dev_exts[i], ext_name))
			return true;
	}

	return false;
}

bool
vk_init_ctx_for_rendering(struct vk_ctx *ctx)
{
	if (!vk_init_ctx(ctx)) {
		fprintf(stdout, "Failed to initialize Vulkan.\n");
		return false;
	}

	if ((ctx->cmd_pool = create_cmd_pool(ctx)) == VK_NULL_HANDLE) {
		fprintf(stderr, "Failed to create command pool.\n");
		goto fail;
	}

	if ((ctx->cmd_buf = create_cmd_buf(ctx->dev, ctx->cmd_pool)) ==
			VK_NULL_HANDLE) {
		fprintf(stderr, "Failed to create command buffer.\n");
		goto fail;
	}

	vkGetDeviceQueue(ctx->dev, ctx->qfam_idx, 0, &ctx->queue);
	if (!ctx->queue) {
		fprintf(stderr, "Failed to get command queue.\n");
		goto fail;
	}

	return true;

fail:
	vk_cleanup_ctx(ctx);
	return false;
}

void
vk_cleanup_ctx(struct vk_ctx *ctx)
{
	if (ctx->cmd_buf != VK_NULL_HANDLE) {
		vkResetCommandBuffer(ctx->cmd_buf, VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
		vkFreeCommandBuffers(ctx->dev, ctx->cmd_pool, 1, &ctx->cmd_buf);
		ctx->cmd_buf = VK_NULL_HANDLE;
	}

	if (ctx->cmd_pool != VK_NULL_HANDLE) {
		vkResetCommandPool(ctx->dev, ctx->cmd_pool, VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT);
		vkDestroyCommandPool(ctx->dev, ctx->cmd_pool, 0);
		ctx->cmd_pool = VK_NULL_HANDLE;
	}

	if (ctx->dev_exts != NULL) {
		free(ctx->dev_exts);
		ctx->dev_exts = NULL;
		ctx->n_dev_exts = 0;
	}

	if (ctx->dev != VK_NULL_HANDLE) {
		vkDestroyDevice(ctx->dev, 0);
		ctx->dev = VK_NULL_HANDLE;
	}

	if (ctx->inst != VK_NULL_HANDLE) {
		vkDestroyInstance(ctx->inst, 0);
		ctx->inst = VK_NULL_HANDLE;
	}
}

bool
vk_create_ext_image(struct vk_ctx *ctx,
		    struct vk_image_props *props, struct vk_image_obj *img)
{
	VkExternalMemoryImageCreateInfo ext_img_info;
	VkImageCreateInfo img_info;

	memset(&ext_img_info, 0, sizeof ext_img_info);
	ext_img_info.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
	ext_img_info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;

	memset(&img_info, 0, sizeof img_info);
	img_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
	img_info.pNext = &ext_img_info;
	img_info.imageType = get_image_type(props->h, props->depth);
	img_info.format = props->format;
	img_info.extent.width = props->w;
	img_info.extent.height = props->h;
	img_info.extent.depth = props->depth;
	img_info.mipLevels = props->num_levels ? props->num_levels : 1;
	img_info.arrayLayers = props->num_layers ? props->num_layers : VK_SAMPLE_COUNT_1_BIT;
	img_info.samples = get_num_samples(props->num_samples);
	img_info.tiling = props->tiling;
	img_info.usage = props->usage;
	img_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
	img_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
	/* issue 17 of EXT_external_objects
	 * Required in OpenGL implementations that support
	 * ARB_texture_view, OES_texture_view, EXT_texture_view,
	 * or OpenGL 4.3 and above.
	 */
	img_info.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;

	if (vkCreateImage(ctx->dev, &img_info, 0, &img->img) != VK_SUCCESS)
		goto fail;

	if(!alloc_image_memory(ctx, img))
		goto fail;

	return true;

fail:
	fprintf(stderr, "Failed to create external image.\n");
	vk_destroy_ext_image(ctx, img);
	img->img = VK_NULL_HANDLE;
	img->mobj.mem = VK_NULL_HANDLE;
	return false;
}

bool
vk_create_ext_buffer(struct vk_ctx *ctx,
		     uint32_t sz,
		     VkBufferUsageFlagBits usage,
		     struct vk_buf *bo)
{
	VkExternalMemoryBufferCreateInfo ext_bo_info;

	memset(&ext_bo_info, 0, sizeof ext_bo_info);
	ext_bo_info.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO;
	ext_bo_info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;

	if (!vk_create_buffer(ctx, true, sz, usage, &ext_bo_info, bo)) {
		fprintf(stderr, "Failed to allocate external buffer.\n");
		return false;
	}

	return true;
}

void
vk_destroy_ext_image(struct vk_ctx *ctx, struct vk_image_obj *img_obj)
{
	if (img_obj->img != VK_NULL_HANDLE) {
		vkDestroyImage(ctx->dev, img_obj->img, 0);
		img_obj->img = VK_NULL_HANDLE;
	}

	if (img_obj->mobj.mem != VK_NULL_HANDLE) {
		vkFreeMemory(ctx->dev, img_obj->mobj.mem, 0);
		img_obj->mobj.mem = VK_NULL_HANDLE;
	}

	if (img_obj->img_view != VK_NULL_HANDLE) {
		vkDestroyImageView(ctx->dev, img_obj->img_view, 0);
		img_obj->img_view = VK_NULL_HANDLE;
	}
}

void
vk_destroy_ext_bo(struct vk_ctx *ctx,
		  struct vk_buf *bo)
{
	if (bo->buf != VK_NULL_HANDLE) {
		vkDestroyBuffer(ctx->dev, bo->buf, 0);
		bo->buf = VK_NULL_HANDLE;
	}

	if (bo->mobj.mem != VK_NULL_HANDLE) {
		vkFreeMemory(ctx->dev, bo->mobj.mem, 0);
		bo->mobj.mem = VK_NULL_HANDLE;
	}
}

bool
vk_fill_ext_image_props(struct vk_ctx *ctx,
			uint32_t w,
			uint32_t h,
			uint32_t d,
			uint32_t num_samples,
			uint32_t num_levels,
			uint32_t num_layers,
			VkFormat format,
			VkImageTiling tiling,
			VkImageLayout in_layout,
			VkImageLayout end_layout,
			bool need_export,
			struct vk_image_props *props)
{
	props->w = w;
	props->h = h;
	props->depth = d;

	props->num_samples = num_samples;
	props->num_levels = num_levels;
	props->num_layers = num_layers;

	props->format = format;
	props->tiling = tiling;

	props->in_layout = in_layout;
	props->end_layout = end_layout;

	props->need_export = need_export;

	if (!are_props_supported(ctx, props))
		return false;

	return true;
}

bool
vk_create_renderer(struct vk_ctx *ctx,
		   const char *vs_src,
		   unsigned int vs_size,
		   const char *fs_src,
		   unsigned int fs_size,
		   bool enable_depth,
		   bool enable_stencil,
		   struct vk_image_att *color_att,
		   struct vk_image_att *depth_att,
		   struct vk_vertex_info *vert_info,
		   struct vk_renderer *renderer)
{
	memset(&renderer->vertex_info, 0, sizeof renderer->vertex_info);
	if (vert_info)
		renderer->vertex_info = *vert_info;

	renderer->renderpass = create_renderpass(ctx, &color_att->props, depth_att ? &depth_att->props : NULL);
	if (renderer->renderpass == VK_NULL_HANDLE)
		goto fail;

	create_framebuffer(ctx, color_att, depth_att, renderer);
	if (renderer->fb == VK_NULL_HANDLE)
		goto fail;

	renderer->vs = create_shader_module(ctx, vs_src, vs_size);
	if (renderer->vs == VK_NULL_HANDLE)
		goto fail;

	renderer->fs = create_shader_module(ctx, fs_src, fs_size);
	if (renderer->fs == VK_NULL_HANDLE)
		goto fail;

	create_pipeline(ctx, color_att->props.w, color_att->props.h,
			color_att->props.num_samples, enable_depth, enable_stencil, renderer);

	if (renderer->pipeline == VK_NULL_HANDLE)
		goto fail;

	return true;

fail:
	fprintf(stderr, "Failed to create graphics pipeline.\n");
	vk_destroy_renderer(ctx, renderer);
	return false;
}

void
vk_destroy_renderer(struct vk_ctx *ctx,
		    struct vk_renderer *renderer)
{
	if (renderer->renderpass != VK_NULL_HANDLE) {
		vkDestroyRenderPass(ctx->dev, renderer->renderpass, 0);
		renderer->renderpass = VK_NULL_HANDLE;
	}

	if (renderer->vs != VK_NULL_HANDLE) {
		vkDestroyShaderModule(ctx->dev, renderer->vs, 0);
		renderer->vs = VK_NULL_HANDLE;
	}

	if (renderer->fs != VK_NULL_HANDLE) {
		vkDestroyShaderModule(ctx->dev, renderer->fs, 0);
		renderer->fs = VK_NULL_HANDLE;
	}

	if (renderer->fb != VK_NULL_HANDLE) {
		vkDestroyFramebuffer(ctx->dev, renderer->fb, 0);
		renderer->fb = VK_NULL_HANDLE;
	}

	if (renderer->pipeline != VK_NULL_HANDLE) {
		vkDestroyPipeline(ctx->dev, renderer->pipeline, 0);
		renderer->pipeline = VK_NULL_HANDLE;
	}

	if (renderer->pipeline_layout != VK_NULL_HANDLE) {
		vkDestroyPipelineLayout(ctx->dev, renderer->pipeline_layout, 0);
		renderer->pipeline_layout = VK_NULL_HANDLE;
	}
}

bool
vk_create_compute_pipeline(struct vk_ctx *ctx,
			   const char *cs_src,
			   unsigned int cs_size,
			   struct vk_descriptor *descriptors,
			   uint32_t n_descriptors,
			   struct vk_compute_pipeline *cp)
{
	memset(cp, 0, sizeof *cp);

	cp->cs = create_shader_module(ctx, cs_src, cs_size);
	if (cp->cs == VK_NULL_HANDLE)
		goto fail;

	VkDescriptorSetLayoutBinding *bindings =
		calloc(n_descriptors, sizeof(*bindings));
	for (uint32_t i = 0; i < n_descriptors; i++) {
		bindings[i].binding = i;
		bindings[i].descriptorType = descriptors[i].type;
		bindings[i].descriptorCount = 1;
		bindings[i].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
	}

	VkDescriptorSetLayoutCreateInfo s_layout_info;
	memset(&s_layout_info, 0, sizeof s_layout_info);
	s_layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
	s_layout_info.bindingCount = n_descriptors,
	s_layout_info.pBindings = bindings;
	if (vkCreateDescriptorSetLayout(ctx->dev, &s_layout_info, NULL, &cp->set_layout) != VK_SUCCESS) {
		fprintf(stderr, "Failed to create descriptor set layout\n");
		cp->set_layout = VK_NULL_HANDLE;
		free(bindings);
		goto fail;
	}

	free(bindings);
	bindings = NULL;

	VkPipelineLayoutCreateInfo p_layout_info;
	memset(&p_layout_info, 0, sizeof p_layout_info);
	p_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
	p_layout_info.setLayoutCount = 1;
	p_layout_info.pSetLayouts = &cp->set_layout;
	if (vkCreatePipelineLayout(ctx->dev, &p_layout_info, NULL, &cp->pipeline_layout) != VK_SUCCESS) {
		fprintf(stderr, "Failed to create pipeline layout\n");
		cp->pipeline_layout = VK_NULL_HANDLE;
		goto fail;
	}

	VkComputePipelineCreateInfo pipeline_info;
	memset(&pipeline_info, 0, sizeof pipeline_info);
	pipeline_info.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
	pipeline_info.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
	pipeline_info.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
	pipeline_info.stage.module = cp->cs;
	pipeline_info.stage.pName = "main";
	pipeline_info.layout = cp->pipeline_layout;
	if (vkCreateComputePipelines(ctx->dev, 0, 1, &pipeline_info, 0, &cp->pipeline) != VK_SUCCESS) {
		fprintf(stderr, "Failed to create graphics pipeline.\n");
		cp->pipeline = VK_NULL_HANDLE;
		goto fail;
	}

	VkDescriptorPoolSize pool_sizes[16];
	uint32_t n_pool_sizes = 0;
	for (uint32_t d = 0; d < n_descriptors; d++) {
		uint32_t ps;
		for (ps = 0; ps < n_pool_sizes; ps++) {
			if (pool_sizes[ps].type == descriptors[d].type)
				break;
		}
		if (ps == n_pool_sizes) {
			assert(n_pool_sizes < ARRAY_SIZE(pool_sizes));
			pool_sizes[ps].type = descriptors[d].type;
			pool_sizes[ps].descriptorCount = 0;
			n_pool_sizes++;
		}

		pool_sizes[ps].descriptorCount++;
	}

	VkDescriptorPoolCreateInfo pool_info;
	memset(&pool_info, 0, sizeof pool_info);
	pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
	pool_info.maxSets = 1;
	pool_info.poolSizeCount = n_pool_sizes;
	pool_info.pPoolSizes = pool_sizes;
	if (vkCreateDescriptorPool(ctx->dev, &pool_info, NULL, &cp->descriptor_pool) != VK_SUCCESS) {
		fprintf(stderr, "Failed to create descriptor pool.\n");
		cp->descriptor_pool = VK_NULL_HANDLE;
		goto fail;
	}

	VkDescriptorSetAllocateInfo set_info;
	memset(&set_info, 0, sizeof set_info);
	set_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
	set_info.descriptorPool = cp->descriptor_pool;
	set_info.descriptorSetCount = 1;
	set_info.pSetLayouts = &cp->set_layout;
	if (vkAllocateDescriptorSets(ctx->dev, &set_info, &cp->descriptor_set) != VK_SUCCESS) {
		fprintf(stderr, "Failed to allocate descriptor set.\n");
		cp->descriptor_set = VK_NULL_HANDLE;
		goto fail;
	}

	for (uint32_t i = 0; i < n_descriptors; i++) {
		VkWriteDescriptorSet desc_write;
		memset(&desc_write, 0, sizeof desc_write);
		desc_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
		desc_write.dstSet = cp->descriptor_set;
		desc_write.dstBinding = i;
		desc_write.dstArrayElement = 0;
		desc_write.descriptorCount = 1;
		desc_write.descriptorType = descriptors[i].type;

		struct VkDescriptorBufferInfo buffer_info;
		switch (descriptors[i].type) {
		case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
		case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
			memset(&buffer_info, 0, sizeof buffer_info);
			buffer_info.buffer = descriptors[i].buf_att->buf.buf;
			buffer_info.offset = descriptors[i].buf_att->offset;
			buffer_info.range = descriptors[i].buf_att->range;
			desc_write.pBufferInfo = &buffer_info;
			break;

		default:
			assert(!"Unknown descriptor type");
			break;
		}
		vkUpdateDescriptorSets(ctx->dev, 1, &desc_write, 0, NULL);
	}

	return true;

fail:
	vk_destroy_compute_pipeline(ctx, cp);
	return false;
}

void
vk_destroy_compute_pipeline(struct vk_ctx *ctx,
			    struct vk_compute_pipeline *cp)
{
	if (cp->cs != VK_NULL_HANDLE) {
		vkDestroyShaderModule(ctx->dev, cp->cs, NULL);
		cp->cs = VK_NULL_HANDLE;
	}

	if (cp->pipeline != VK_NULL_HANDLE) {
		vkDestroyPipeline(ctx->dev, cp->pipeline, NULL);
		cp->pipeline = VK_NULL_HANDLE;
	}

	if (cp->descriptor_pool != VK_NULL_HANDLE) {
		vkDestroyDescriptorPool(ctx->dev, cp->descriptor_pool, NULL);
		cp->descriptor_pool = VK_NULL_HANDLE;
		cp->descriptor_set = VK_NULL_HANDLE;
	}

	if (cp->pipeline_layout != VK_NULL_HANDLE) {
		vkDestroyPipelineLayout(ctx->dev, cp->pipeline_layout, NULL);
		cp->pipeline_layout = VK_NULL_HANDLE;
	}

	if (cp->set_layout != VK_NULL_HANDLE) {
		vkDestroyDescriptorSetLayout(ctx->dev, cp->set_layout, NULL);
		cp->set_layout = VK_NULL_HANDLE;
	}
}

bool
vk_create_buffer(struct vk_ctx *ctx,
		 bool is_external,
		 uint32_t sz,
		 VkBufferUsageFlagBits usage,
		 void *pnext,
		 struct vk_buf *bo)
{
	VkBufferCreateInfo buf_info;
	VkMemoryRequirements mem_reqs;

	bo->mobj.mem = VK_NULL_HANDLE;
	bo->buf = VK_NULL_HANDLE;

	/* VkBufferCreateInfo */
	memset(&buf_info, 0, sizeof buf_info);
	buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
	buf_info.size = sz;
	buf_info.usage = usage;
	buf_info.pNext = pnext;
	buf_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

	if (vkCreateBuffer(ctx->dev, &buf_info, 0, &bo->buf) != VK_SUCCESS)
		goto fail;

	/* allocate buffer */
	vkGetBufferMemoryRequirements(ctx->dev, bo->buf, &mem_reqs);
	/* VK_MEMORY_PROPERTY_HOST_COHERENT_BIT bit specifies that the
	 * host cache management commands vkFlushMappedMemoryRanges and
	 * vkInvalidateMappedMemoryRanges are not needed to flush host
	 * writes to the device or make device writes visible to the
	 * host, respectively. */
	bo->mobj.mem = alloc_memory(ctx, is_external, &mem_reqs, VK_NULL_HANDLE, VK_NULL_HANDLE,
				    VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
				    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);

	if (bo->mobj.mem == VK_NULL_HANDLE)
		goto fail;

	bo->mobj.mem_sz = sz;

	if (vkBindBufferMemory(ctx->dev, bo->buf, bo->mobj.mem, 0) != VK_SUCCESS) {
		fprintf(stderr, "Failed to bind buffer memory.\n");
		goto fail;
	}

	return true;

fail:
	fprintf(stderr, "Failed to allocate buffer.\n");
	vk_destroy_buffer(ctx, bo);
	return false;
}

bool
vk_update_buffer_data(struct vk_ctx *ctx,
		      const void *data,
		      uint32_t data_sz,
		      struct vk_buf *bo)
{
	void *map;

	if (vkMapMemory(ctx->dev, bo->mobj.mem, 0, data_sz, 0, &map) != VK_SUCCESS) {
		fprintf(stderr, "Failed to map buffer memory.\n");
		goto fail;
	}

	memcpy(map, data, data_sz);

	vkUnmapMemory(ctx->dev, bo->mobj.mem);
	return true;

fail:
	fprintf(stderr, "Failed to update buffer data. Destroying the buffer.\n");
	vk_destroy_buffer(ctx, bo);

	return false;
}

bool
vk_get_buffer_data(struct vk_ctx *ctx,
		   void *data,
		   uint32_t data_sz,
		   struct vk_buf *bo)
{
	void *map;

	if (vkMapMemory(ctx->dev, bo->mobj.mem, 0, data_sz, 0, &map) != VK_SUCCESS) {
		fprintf(stderr, "Failed to map buffer memory.\n");
		return false;
	}

	memcpy(data, map, data_sz);

	vkUnmapMemory(ctx->dev, bo->mobj.mem);
	return true;
}

void
vk_destroy_buffer(struct vk_ctx *ctx,
		  struct vk_buf *bo)
{
	if (bo->buf != VK_NULL_HANDLE)
		vkDestroyBuffer(ctx->dev, bo->buf, 0);

	if (bo->mobj.mem != VK_NULL_HANDLE)
		vkFreeMemory(ctx->dev, bo->mobj.mem, 0);

	bo->mobj.mem_sz = 0;
	bo->buf = VK_NULL_HANDLE;
	bo->mobj.mem = VK_NULL_HANDLE;
}

void
vk_draw(struct vk_ctx *ctx,
	struct vk_buf *vbo,
	struct vk_renderer *renderer,
	float *vk_fb_color,
	uint32_t vk_fb_color_count,
	struct vk_semaphores *semaphores,
	bool has_wait, bool has_signal,
	struct vk_image_att *attachments,
	uint32_t n_attachments,
	float x, float y,
	float w, float h)
{
	VkCommandBufferBeginInfo cmd_begin_info;
	VkRenderPassBeginInfo rp_begin_info;
	VkRect2D rp_area;
	VkClearValue clear_values[2];
	VkSubmitInfo submit_info;
	VkDeviceSize offsets[] = {0};
	VkPipelineStageFlagBits stage_flags;
	struct vk_dims img_size;

	assert(vk_fb_color_count == 4);
	if (has_wait)
		assert(semaphores->gl_frame_done);
	if (has_signal)
		assert(semaphores->vk_frame_ready);

	/* VkCommandBufferBeginInfo */
	memset(&cmd_begin_info, 0, sizeof cmd_begin_info);
	cmd_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
	cmd_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

	/* VkRect2D render area */
	memset(&rp_area, 0, sizeof rp_area);
	rp_area.extent.width = (uint32_t)w;
	rp_area.extent.height = (uint32_t)h;
	rp_area.offset.x = x;
	rp_area.offset.y = y;

	/* VkClearValue */
	memset(&clear_values[0], 0, sizeof clear_values[0]);
	clear_values[0].color.float32[0] = vk_fb_color[0]; /* red */
	clear_values[0].color.float32[1] = vk_fb_color[1]; /* green */
	clear_values[0].color.float32[2] = vk_fb_color[2]; /* blue */
	clear_values[0].color.float32[3] = vk_fb_color[3]; /* alpha */

	memset(&clear_values[1], 0, sizeof clear_values[1]);
	clear_values[1].depthStencil.depth = 1.0;
	clear_values[1].depthStencil.stencil = 0;

	/* VkRenderPassBeginInfo */
	memset(&rp_begin_info, 0, sizeof rp_begin_info);
	rp_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
	rp_begin_info.renderPass = renderer->renderpass;
	rp_begin_info.framebuffer = renderer->fb;
	rp_begin_info.renderArea = rp_area;
	rp_begin_info.clearValueCount = 2;
	rp_begin_info.pClearValues = clear_values;

	/* VkSubmitInfo */
	stage_flags = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;

	memset(&submit_info, 0, sizeof submit_info);
	submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
	submit_info.commandBufferCount = 1;
	submit_info.pCommandBuffers = &ctx->cmd_buf;
	if (has_wait) {
		submit_info.pWaitDstStageMask = &stage_flags;
		submit_info.waitSemaphoreCount = 1;
		submit_info.pWaitSemaphores = &semaphores->gl_frame_done;
	}

	if (has_signal) {
		submit_info.signalSemaphoreCount = 1;
		submit_info.pSignalSemaphores = &semaphores->vk_frame_ready;
	}

	vkBeginCommandBuffer(ctx->cmd_buf, &cmd_begin_info);
	vkCmdBeginRenderPass(ctx->cmd_buf, &rp_begin_info, VK_SUBPASS_CONTENTS_INLINE);

	viewport.x = x;
	viewport.y = y;
	viewport.width = w;
	viewport.height = h;

	scissor.offset.x = x;
	scissor.offset.y = y;
	scissor.extent.width = w;
	scissor.extent.height = h;

	vkCmdSetViewport(ctx->cmd_buf, 0, 1, &viewport);
	vkCmdSetScissor(ctx->cmd_buf, 0, 1, &scissor);

	img_size.w = (float)w;
	img_size.h = (float)h;
	vkCmdPushConstants(ctx->cmd_buf,
			   renderer->pipeline_layout,
			   VK_SHADER_STAGE_FRAGMENT_BIT,
			   0, sizeof (struct vk_dims),
			   &img_size);

	if (vbo) {
		vkCmdBindVertexBuffers(ctx->cmd_buf, 0, 1, &vbo->buf, offsets);
	}
	vkCmdBindPipeline(ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->pipeline);

	int num_vertices = vbo ? renderer->vertex_info.num_verts : 4;
	vkCmdDraw(ctx->cmd_buf, num_vertices, 1, 0, 0);

	vkCmdEndRenderPass(ctx->cmd_buf);
	if (attachments) {
		VkImageMemoryBarrier *barriers =
			calloc(n_attachments, sizeof(VkImageMemoryBarrier));
		VkImageMemoryBarrier *barrier = barriers;
		for (uint32_t n = 0; n < n_attachments; n++, barrier++) {
			struct vk_image_att *att = &attachments[n];
			VkImageAspectFlagBits depth_stencil_flags =
				get_aspect_from_depth_format(att->props.format);
			bool is_depth = (depth_stencil_flags != 0);

			/* Insert barrier to mark ownership transfer. */
			barrier->sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
			barrier->oldLayout = is_depth ?
				VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL :
				VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
			barrier->newLayout = VK_IMAGE_LAYOUT_GENERAL;
			barrier->srcAccessMask = get_access_mask(barrier->oldLayout);
			barrier->dstAccessMask = get_access_mask(barrier->newLayout);
			barrier->srcQueueFamilyIndex = ctx->qfam_idx;
			barrier->dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL;
			barrier->image = att->obj.img;
			barrier->subresourceRange.aspectMask = is_depth ?
				depth_stencil_flags :
				VK_IMAGE_ASPECT_COLOR_BIT;
			barrier->subresourceRange.baseMipLevel = 0;
			barrier->subresourceRange.levelCount = 1;
			barrier->subresourceRange.baseArrayLayer = 0;
			barrier->subresourceRange.layerCount = 1;
		}

		vkCmdPipelineBarrier(ctx->cmd_buf,
				     VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
				     VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
				     0,
				     0, NULL,
				     0, NULL,
				     n_attachments, barriers);
		free(barriers);
	}
	vkEndCommandBuffer(ctx->cmd_buf);

	if (vkQueueSubmit(ctx->queue, 1, &submit_info, VK_NULL_HANDLE) != VK_SUCCESS) {
		fprintf(stderr, "Failed to submit queue.\n");
	}

	/* FIXME */
	if (!semaphores && !has_wait && !has_signal)
		vkQueueWaitIdle(ctx->queue);
}

void
vk_clear_color(struct vk_ctx *ctx,
	       struct vk_buf *vbo,
	       struct vk_renderer *renderer,
	       float *vk_fb_color,
	       uint32_t vk_fb_color_count,
	       struct vk_semaphores *semaphores,
	       bool has_wait, bool has_signal,
	       struct vk_image_att *attachments,
	       uint32_t n_attachments,
	       float x, float y,
	       float w, float h)
{
	VkCommandBufferBeginInfo cmd_begin_info;
	VkRenderPassBeginInfo rp_begin_info;
	VkRect2D rp_area;
	VkClearValue clear_values[2];
	VkSubmitInfo submit_info;
	VkPipelineStageFlagBits stage_flags;
	VkImageSubresourceRange img_range;

	assert(vk_fb_color_count == 4);

	if (has_wait)
		assert(semaphores->gl_frame_done);
	if (has_signal)
		assert(semaphores->vk_frame_ready);

	/* VkCommandBufferBeginInfo */
	memset(&cmd_begin_info, 0, sizeof cmd_begin_info);
	cmd_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
	cmd_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

	/* Make sure whatever we had going is done. */
	vkQueueWaitIdle(ctx->queue);

	/* VkRect2D render area */
	memset(&rp_area, 0, sizeof rp_area);
	rp_area.extent.width = (uint32_t)w;
	rp_area.extent.height = (uint32_t)h;
	rp_area.offset.x = x;
	rp_area.offset.y = y;

	/* VkClearValue */
	memset(&clear_values[0], 0, sizeof clear_values[0]);
	clear_values[0].color.float32[0] = vk_fb_color[0]; /* red */
	clear_values[0].color.float32[1] = vk_fb_color[1]; /* green */
	clear_values[0].color.float32[2] = vk_fb_color[2]; /* blue */
	clear_values[0].color.float32[3] = vk_fb_color[3]; /* alpha */

	memset(&clear_values[1], 0, sizeof clear_values[1]);
	clear_values[1].depthStencil.depth = 1.0;
	clear_values[1].depthStencil.stencil = 0;

	/* VkRenderPassBeginInfo */
	memset(&rp_begin_info, 0, sizeof rp_begin_info);
	rp_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
	rp_begin_info.renderPass = renderer->renderpass;
	rp_begin_info.framebuffer = renderer->fb;
	rp_begin_info.renderArea = rp_area;
	rp_begin_info.clearValueCount = 2;
	rp_begin_info.pClearValues = clear_values;

	/* VkSubmitInfo */
	stage_flags = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;

	memset(&submit_info, 0, sizeof submit_info);
	submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
	submit_info.commandBufferCount = 1;
	submit_info.pCommandBuffers = &ctx->cmd_buf;
	if (has_wait) {
		submit_info.pWaitDstStageMask = &stage_flags;
		submit_info.waitSemaphoreCount = 1;
		submit_info.pWaitSemaphores = &semaphores->gl_frame_done;
	}

	if (has_signal) {
		submit_info.signalSemaphoreCount = 1;
		submit_info.pSignalSemaphores = &semaphores->vk_frame_ready;
	}

	img_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
	img_range.baseMipLevel = 0;
	img_range.levelCount = 1;
	img_range.baseArrayLayer = 0;
	img_range.layerCount = 1;

	vkBeginCommandBuffer(ctx->cmd_buf, &cmd_begin_info);
	vk_transition_image_layout(&attachments[0],
				   ctx->cmd_buf,
				   VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
				   VK_IMAGE_LAYOUT_GENERAL,
				   VK_QUEUE_FAMILY_EXTERNAL,
				   ctx->qfam_idx);
	vkCmdClearColorImage(ctx->cmd_buf,
			     attachments[0].obj.img,
			     VK_IMAGE_LAYOUT_GENERAL,
			     &clear_values[0].color,
			     1,
			     &img_range);

	vkCmdBeginRenderPass(ctx->cmd_buf, &rp_begin_info, VK_SUBPASS_CONTENTS_INLINE);

	viewport.x = x;
	viewport.y = y;
	viewport.width = w;
	viewport.height = h;

	scissor.offset.x = x;
	scissor.offset.y = y;
	scissor.extent.width = w;
	scissor.extent.height = h;

	vkCmdSetViewport(ctx->cmd_buf, 0, 1, &viewport);
	vkCmdSetScissor(ctx->cmd_buf, 0, 1, &scissor);

	vkCmdBindPipeline(ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->pipeline);

	vkCmdEndRenderPass(ctx->cmd_buf);

	VkImageMemoryBarrier *barriers =
		calloc(n_attachments, sizeof(VkImageMemoryBarrier));
	VkImageMemoryBarrier *barrier = barriers;

	for (uint32_t n = 0; n < n_attachments; n++, barrier++) {
		struct vk_image_att *att = &attachments[n];

		/* Insert barrier to mark ownership transfer. */
		barrier->sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;

		bool is_depth =
			get_aspect_from_depth_format(att->props.format) != (uintptr_t)VK_NULL_HANDLE;

		barrier->oldLayout = is_depth ?
			VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL :
			VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
		barrier->newLayout = VK_IMAGE_LAYOUT_GENERAL;
		barrier->srcAccessMask = get_access_mask(barrier->oldLayout);
		barrier->dstAccessMask = get_access_mask(barrier->newLayout);
		barrier->srcQueueFamilyIndex = ctx->qfam_idx;
		barrier->dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL;
		barrier->image = att->obj.img;
		barrier->subresourceRange.aspectMask = is_depth ?
			VK_IMAGE_ASPECT_DEPTH_BIT :
			VK_IMAGE_ASPECT_COLOR_BIT;
		barrier->subresourceRange.baseMipLevel = 0;
		barrier->subresourceRange.levelCount = 1;
		barrier->subresourceRange.baseArrayLayer = 0;
		barrier->subresourceRange.layerCount = 1;
	}

	vkCmdPipelineBarrier(ctx->cmd_buf,
					VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
					VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
					0,
					0, NULL,
					0, NULL,
					n_attachments, barriers);
	free(barriers);

	vkEndCommandBuffer(ctx->cmd_buf);

	if (vkQueueSubmit(ctx->queue, 1, &submit_info, VK_NULL_HANDLE) != VK_SUCCESS) {
		fprintf(stderr, "Failed to submit queue.\n");
	}

	if (!semaphores && !has_wait && !has_signal)
		vkQueueWaitIdle(ctx->queue);
}

void
vk_copy_image_to_buffer(struct vk_ctx *ctx,
			struct vk_image_att *src_img,
			struct vk_buf *dst_bo,
			float w, float h)
{
	VkCommandBufferBeginInfo cmd_begin_info;
	VkSubmitInfo submit_info;
	VkImageAspectFlagBits aspect_mask = get_aspect_from_depth_format(src_img->props.format);

	/* VkCommandBufferBeginInfo */
	memset(&cmd_begin_info, 0, sizeof cmd_begin_info);
	cmd_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
	cmd_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

	/* Make sure whatever we had going is done. */
	vkQueueWaitIdle(ctx->queue);

	memset(&submit_info, 0, sizeof submit_info);
	submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
	submit_info.commandBufferCount = 1;
	submit_info.pCommandBuffers = &ctx->cmd_buf;

	vkBeginCommandBuffer(ctx->cmd_buf, &cmd_begin_info);
	if (src_img->props.end_layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL && dst_bo) {
		vk_transition_image_layout(src_img,
					   ctx->cmd_buf,
					   VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
					   VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
					   VK_QUEUE_FAMILY_EXTERNAL,
					   ctx->qfam_idx);

		/* copy image to buf */
		VkBufferImageCopy copy_region = {
			.bufferOffset = 0,
			.bufferRowLength = w,
			.bufferImageHeight = h,
			.imageSubresource = {
				.aspectMask = aspect_mask ? aspect_mask : VK_IMAGE_ASPECT_COLOR_BIT,
				.mipLevel = 0,
				.baseArrayLayer = 0,
				.layerCount = 1,
			},
			.imageOffset = { 0, 0, 0 },
			.imageExtent = { w, h, 1 }
                };

		vkCmdCopyImageToBuffer(ctx->cmd_buf,
				       src_img->obj.img,
				       VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
				       dst_bo->buf, 1, &copy_region);

		vk_transition_image_layout(src_img,
					   ctx->cmd_buf,
					   VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
					   VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
					   VK_QUEUE_FAMILY_EXTERNAL,
					   ctx->qfam_idx);

		VkBufferMemoryBarrier write_finish_buffer_memory_barrier = {
			.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
			.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
			.dstAccessMask = VK_ACCESS_HOST_READ_BIT,
			.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
			.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
			.buffer = dst_bo->buf,
			.offset = 0,
			.size = VK_WHOLE_SIZE
		};

		vkCmdPipelineBarrier(ctx->cmd_buf,
				VK_PIPELINE_STAGE_TRANSFER_BIT,
				VK_PIPELINE_STAGE_HOST_BIT,
				(VkDependencyFlags) 0, 0, NULL,
				1, &write_finish_buffer_memory_barrier,
				0, NULL);
	}
	vkEndCommandBuffer(ctx->cmd_buf);

	if (vkQueueSubmit(ctx->queue, 1, &submit_info, VK_NULL_HANDLE) != VK_SUCCESS) {
		fprintf(stderr, "Failed to submit queue.\n");
	}
	vkQueueWaitIdle(ctx->queue);
}

bool
vk_create_semaphores(struct vk_ctx *ctx,
		     struct vk_semaphores *semaphores)
{
	VkSemaphoreCreateInfo sema_info;
	VkExportSemaphoreCreateInfo exp_sema_info;

	/* VkExportSemaphoreCreateInfo */
	memset(&exp_sema_info, 0, sizeof exp_sema_info);
	exp_sema_info.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
	exp_sema_info.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;

	/* VkSemaphoreCreateInfo */
	memset(&sema_info, 0, sizeof sema_info);
	sema_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
	sema_info.pNext = &exp_sema_info;

	if (vkCreateSemaphore(ctx->dev, &sema_info, 0, &semaphores->vk_frame_ready) != VK_SUCCESS) {
		fprintf(stderr, "Failed to create semaphore vk_frame_ready.\n");
		return false;
	}

	if (vkCreateSemaphore(ctx->dev, &sema_info, 0, &semaphores->gl_frame_done) != VK_SUCCESS) {
		fprintf(stderr, "Failed to create semaphore gl_frame_done.\n");
		return false;
	}

	return true;
}

void
vk_destroy_semaphores(struct vk_ctx *ctx,
		      struct vk_semaphores *semaphores)
{
	if (semaphores->vk_frame_ready)
		vkDestroySemaphore(ctx->dev, semaphores->vk_frame_ready, 0);
	if (semaphores->gl_frame_done)
		vkDestroySemaphore(ctx->dev, semaphores->gl_frame_done, 0);
}

void
vk_transition_image_layout(struct vk_image_att *img_att,
			   VkCommandBuffer cmd_buf,
			   VkImageLayout old_layout,
			   VkImageLayout new_layout,
			   uint32_t src_queue_fam_idx,
			   uint32_t dst_queue_fam_idx)
{
	VkImageMemoryBarrier barrier;
	struct vk_image_props props = img_att->props;
	VkImageAspectFlagBits aspect_mask = get_aspect_from_depth_format(props.format);

	memset(&barrier, 0, sizeof barrier);
	barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
	barrier.srcAccessMask = get_access_mask(old_layout);
	barrier.dstAccessMask = get_access_mask(new_layout);
	barrier.oldLayout = old_layout;
	barrier.newLayout = new_layout;
	barrier.srcQueueFamilyIndex = src_queue_fam_idx;
	barrier.dstQueueFamilyIndex = dst_queue_fam_idx;
	barrier.image = img_att->obj.img;
	barrier.subresourceRange.aspectMask = aspect_mask ? aspect_mask : VK_IMAGE_ASPECT_COLOR_BIT;
	barrier.subresourceRange.levelCount = 1;
	barrier.subresourceRange.layerCount = 1;

	vkCmdPipelineBarrier(cmd_buf,
			     get_pipeline_stage_flags(old_layout),
			     get_pipeline_stage_flags(new_layout),
			     0, 0, VK_NULL_HANDLE, 0, VK_NULL_HANDLE, 1, &barrier);
}
