#include <assert.h>
#include <fcntl.h>
#include <poll.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/types.h>
#include <unistd.h>
#include <drm_fourcc.h>
#include <vulkan/vulkan.h>
#include <wayland-util.h>
#include <wlr/render/color.h>
#include <wlr/render/interface.h>
#include <wlr/types/wlr_drm.h>
#include <wlr/util/box.h>
#include <wlr/util/log.h>
#include <wlr/render/drm_syncobj.h>
#include <wlr/render/vulkan.h>
#include <wlr/backend/interface.h>
#include <wlr/types/wlr_linux_dmabuf_v1.h>
#include <xf86drm.h>

#include "render/dmabuf.h"
#include "render/pixel_format.h"
#include "render/vulkan.h"
#include "render/vulkan/shaders/common.vert.h"
#include "render/vulkan/shaders/texture.frag.h"
#include "render/vulkan/shaders/quad.frag.h"
#include "render/vulkan/shaders/output.frag.h"
#include "util/array.h"

// TODO:
// - use a pipeline cache (not sure when to save though, after every pipeline
//   creation?)
// - create pipelines as derivatives of each other
// - evaluate if creating VkDeviceMemory pools is a good idea.
//   We can expect wayland client images to be fairly large (and shouldn't
//   have more than 4k of those I guess) but pooling memory allocations
//   might still be a good idea.

static const VkDeviceSize min_stage_size = 1024 * 1024; // 1MB
static const VkDeviceSize max_stage_size = 256 * min_stage_size; // 256MB
static const size_t start_descriptor_pool_size = 256u;
static bool default_debug = true;

static const struct wlr_renderer_impl renderer_impl;

bool wlr_renderer_is_vk(const struct wlr_renderer *wlr_renderer) {
	return wlr_renderer->impl == &renderer_impl;
}

struct wlr_vk_renderer *vulkan_get_renderer(struct wlr_renderer *wlr_renderer) {
	assert(wlr_renderer_is_vk(wlr_renderer));
	struct wlr_vk_renderer *renderer = wl_container_of(wlr_renderer, renderer, wlr_renderer);
	return renderer;
}

static struct wlr_vk_render_format_setup *find_or_create_render_setup(
		struct wlr_vk_renderer *renderer, const struct wlr_vk_format *format,
		bool has_blending_buffer, bool srgb);

static struct wlr_vk_descriptor_pool *alloc_ds(
		struct wlr_vk_renderer *renderer, VkDescriptorSet *ds,
		VkDescriptorType type, const VkDescriptorSetLayout *layout,
		struct wl_list *pool_list, size_t *last_pool_size) {
	VkResult res;

	VkDescriptorSetAllocateInfo ds_info = {
		.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
		.descriptorSetCount = 1,
		.pSetLayouts = layout,
	};

	struct wlr_vk_descriptor_pool *pool;
	wl_list_for_each(pool, pool_list, link) {
		if (pool->free > 0) {
			ds_info.descriptorPool = pool->pool;
			res = vkAllocateDescriptorSets(renderer->dev->dev, &ds_info, ds);
			switch (res) {
			case VK_ERROR_FRAGMENTED_POOL:
			case VK_ERROR_OUT_OF_POOL_MEMORY:
				// Descriptor sets with more than one descriptor can cause us
				// to run out of pool memory early or lead to fragmentation
				// that makes the pool unable to service our allocation
				// request. Try the next pool or allocate a new one.
				continue;
			case VK_SUCCESS:
				--pool->free;
				return pool;
			default:
				wlr_vk_error("vkAllocateDescriptorSets", res);
				return NULL;
			}
		}
	}

	pool = calloc(1, sizeof(*pool));
	if (!pool) {
		wlr_log_errno(WLR_ERROR, "allocation failed");
		return NULL;
	}

	size_t count = 2 * (*last_pool_size);
	if (!count) {
		count = start_descriptor_pool_size;
	}

	pool->free = count;
	VkDescriptorPoolSize pool_size = {
		.descriptorCount = count,
		.type = type,
	};

	VkDescriptorPoolCreateInfo dpool_info = {
		.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
		.maxSets = count,
		.poolSizeCount = 1,
		.pPoolSizes = &pool_size,
		.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
	};

	res = vkCreateDescriptorPool(renderer->dev->dev, &dpool_info, NULL,
		&pool->pool);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateDescriptorPool", res);
		free(pool);
		return NULL;
	}

	*last_pool_size = count;
	wl_list_insert(pool_list, &pool->link);

	ds_info.descriptorPool = pool->pool;
	res = vkAllocateDescriptorSets(renderer->dev->dev, &ds_info, ds);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkAllocateDescriptorSets", res);
		return NULL;
	}

	--pool->free;
	return pool;
}

struct wlr_vk_descriptor_pool *vulkan_alloc_texture_ds(
		struct wlr_vk_renderer *renderer, VkDescriptorSetLayout ds_layout,
		VkDescriptorSet *ds) {
	return alloc_ds(renderer, ds, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
		&ds_layout, &renderer->descriptor_pools,
		&renderer->last_pool_size);
}

struct wlr_vk_descriptor_pool *vulkan_alloc_blend_ds(
	struct wlr_vk_renderer *renderer, VkDescriptorSet *ds) {
	return alloc_ds(renderer, ds, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
		&renderer->output_ds_srgb_layout, &renderer->output_descriptor_pools,
		&renderer->last_output_pool_size);
}

void vulkan_free_ds(struct wlr_vk_renderer *renderer,
		struct wlr_vk_descriptor_pool *pool, VkDescriptorSet ds) {
	vkFreeDescriptorSets(renderer->dev->dev, pool->pool, 1, &ds);
	++pool->free;
}

static void destroy_render_format_setup(struct wlr_vk_renderer *renderer,
		struct wlr_vk_render_format_setup *setup) {
	if (!setup) {
		return;
	}

	VkDevice dev = renderer->dev->dev;
	vkDestroyRenderPass(dev, setup->render_pass, NULL);
	vkDestroyRenderPass(dev, setup->render_pass_clear, NULL);
	vkDestroyPipeline(dev, setup->output_pipe_identity, NULL);
	vkDestroyPipeline(dev, setup->output_pipe_srgb, NULL);
	vkDestroyPipeline(dev, setup->output_pipe_pq, NULL);
	vkDestroyPipeline(dev, setup->output_pipe_lut3d, NULL);
	vkDestroyPipeline(dev, setup->output_pipe_gamma22, NULL);
	vkDestroyPipeline(dev, setup->output_pipe_bt1886, NULL);

	struct wlr_vk_pipeline *pipeline, *tmp_pipeline;
	wl_list_for_each_safe(pipeline, tmp_pipeline, &setup->pipelines, link) {
		vkDestroyPipeline(dev, pipeline->vk, NULL);
		free(pipeline);
	}

	free(setup);
}

static void stage_buffer_destroy(struct wlr_vk_renderer *r,
		struct wlr_vk_stage_buffer *buffer) {
	if (!buffer) {
		return;
	}

	wl_array_release(&buffer->watermarks);
	if (buffer->cpu_mapping) {
		vkUnmapMemory(r->dev->dev, buffer->memory);
		buffer->cpu_mapping = NULL;
	}
	if (buffer->buffer) {
		vkDestroyBuffer(r->dev->dev, buffer->buffer, NULL);
	}
	if (buffer->memory) {
		vkFreeMemory(r->dev->dev, buffer->memory, NULL);
	}

	wl_list_remove(&buffer->link);
	free(buffer);
}

static struct wlr_vk_stage_buffer *stage_buffer_create(
		struct wlr_vk_renderer *r, VkDeviceSize bsize) {
	struct wlr_vk_stage_buffer *buf = calloc(1, sizeof(*buf));
	if (!buf) {
		wlr_log_errno(WLR_ERROR, "Allocation failed");
		return NULL;
	}

	wl_list_init(&buf->link);

	VkResult res;
	VkBufferCreateInfo buf_info = {
		.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
		.size = bsize,
		.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT |
			VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
		.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
	};
	res = vkCreateBuffer(r->dev->dev, &buf_info, NULL, &buf->buffer);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateBuffer", res);
		goto error;
	}

	VkMemoryRequirements mem_reqs;
	vkGetBufferMemoryRequirements(r->dev->dev, buf->buffer, &mem_reqs);

	int mem_type_index = vulkan_find_mem_type(r->dev,
		VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
		VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, mem_reqs.memoryTypeBits);
	if (mem_type_index < 0) {
		wlr_log(WLR_ERROR, "Failed to find memory type");
		goto error;
	}

	VkMemoryAllocateInfo mem_info = {
		.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
		.allocationSize = mem_reqs.size,
		.memoryTypeIndex = (uint32_t)mem_type_index,
	};
	res = vkAllocateMemory(r->dev->dev, &mem_info, NULL, &buf->memory);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkAllocateMemory", res);
		goto error;
	}

	res = vkBindBufferMemory(r->dev->dev, buf->buffer, buf->memory, 0);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkBindBufferMemory", res);
		goto error;
	}

	res = vkMapMemory(r->dev->dev, buf->memory, 0, VK_WHOLE_SIZE, 0, &buf->cpu_mapping);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkMapMemory", res);
		goto error;
	}

	buf->buf_size = bsize;
	return buf;

error:
	stage_buffer_destroy(r, buf);
	return NULL;
}

void vulkan_stage_buffer_reclaim(struct wlr_vk_stage_buffer *buf,
		uint64_t current_point) {

	size_t completed = 0;
	struct wlr_vk_stage_watermark *mark;
	wl_array_for_each(mark, &buf->watermarks) {
		if (mark->timeline_point > current_point) {
			break;
		}
		buf->tail = mark->head;
		completed++;
	}

	if (completed > 0) {
		completed *= sizeof(struct wlr_vk_stage_watermark);
		if (completed == buf->watermarks.size) {
			buf->watermarks.size = 0;
		} else {
			array_remove_at(&buf->watermarks, 0, completed);
		}
	}
}

VkDeviceSize vulkan_stage_buffer_alloc(struct wlr_vk_stage_buffer *buf,
		VkDeviceSize size, VkDeviceSize alignment) {
	VkDeviceSize head = buf->head;

	// Round up to the next multiple of alignment
	VkDeviceSize rem = head % alignment;
	if (rem != 0) {
		head += alignment - rem;
	}

	VkDeviceSize end = head >= buf->tail ? buf->buf_size : buf->tail;
	if (head + size < end) {
		// Regular allocation head till end of available space
		buf->head = head + size;
		return head;
	} else if (size < buf->tail && head >= buf->tail) {
		// First allocation after wrap-around
		buf->head = size;
		return 0;
	}

	return (VkDeviceSize)-1;
}

struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r,
		VkDeviceSize size, VkDeviceSize alignment) {
	if (size >= max_stage_size) {
		wlr_log(WLR_ERROR, "cannot allocate stage buffer: "
			"requested size (%zu bytes) exceeds maximum (%zu bytes)",
			(size_t)size, (size_t)max_stage_size-1);
		goto error;
	}

	VkDeviceSize max_buf_size = min_stage_size / 2;
	struct wlr_vk_stage_buffer *buf;
	wl_list_for_each(buf, &r->stage.buffers, link) {
		VkDeviceSize offset = vulkan_stage_buffer_alloc(buf, size, alignment);
		if (offset != (VkDeviceSize)-1) {
			return (struct wlr_vk_buffer_span) {
				.buffer = buf,
				.offset = offset,
				.size = size,
			};
		}
		if (buf->buf_size > max_buf_size) {
			max_buf_size = buf->buf_size;
		}
	}

	VkDeviceSize bsize = max_buf_size * 2;
	while (size * 2 > bsize) {
		bsize *= 2;
	}
	if (bsize > max_stage_size) {
		wlr_log(WLR_INFO, "vulkan stage buffer has reached max size");
		bsize = max_stage_size;
	}

	struct wlr_vk_stage_buffer *new_buf = stage_buffer_create(r, bsize);
	if (new_buf == NULL) {
		goto error;
	}

	wl_list_insert(r->stage.buffers.prev, &new_buf->link);

	VkDeviceSize offset = vulkan_stage_buffer_alloc(new_buf, size, alignment);
	assert(offset != (VkDeviceSize)-1);

	return (struct wlr_vk_buffer_span) {
		.buffer = new_buf,
		.offset = offset,
		.size = size,
	};

error:
	return (struct wlr_vk_buffer_span) {
		.buffer = NULL,
		.offset = 0,
		.size = 0,
	};
}

void vulkan_stage_mark_submit(struct wlr_vk_renderer *renderer,
		uint64_t timeline_point) {
	struct wlr_vk_stage_buffer *buf;
	wl_list_for_each(buf, &renderer->stage.buffers, link) {
		if (buf->head == buf->tail) {
			continue;
		}

		struct wlr_vk_stage_watermark *mark = wl_array_add(
			&buf->watermarks, sizeof(*mark));
		if (mark == NULL) {
			wlr_log_errno(WLR_ERROR, "Allocation failed");
			continue;
		}

		*mark = (struct wlr_vk_stage_watermark){
			.head = buf->head,
			.timeline_point = timeline_point,
		};
	}
}

static void stage_buffer_gc(struct wlr_vk_renderer *renderer, uint64_t current_point) {
	struct wlr_vk_stage_buffer *buf, *buf_tmp;
	wl_list_for_each_safe(buf, buf_tmp, &renderer->stage.buffers, link) {
		if (buf->head != buf->tail) {
			buf->empty_gc_cnt = 0;
			vulkan_stage_buffer_reclaim(buf, current_point);
			continue;
		}
		if (buf->buf_size <= min_stage_size) {
			// We will not deallocate the first buffer
			continue;
		}

		buf->empty_gc_cnt++;
		if (buf->empty_gc_cnt >= 1000) {
			// This buffer hasn't been used for a while, so let's deallocate it
			stage_buffer_destroy(renderer, buf);
		}
	}
}

VkCommandBuffer vulkan_record_stage_cb(struct wlr_vk_renderer *renderer) {
	if (renderer->stage.cb == NULL) {
		renderer->stage.cb = vulkan_acquire_command_buffer(renderer);
		if (renderer->stage.cb == NULL) {
			return VK_NULL_HANDLE;
		}

		VkCommandBufferBeginInfo begin_info = {
			.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
		};
		vkBeginCommandBuffer(renderer->stage.cb->vk, &begin_info);
	}

	return renderer->stage.cb->vk;
}

VkSemaphore vulkan_command_buffer_wait_sync_file(struct wlr_vk_renderer *renderer,
		struct wlr_vk_command_buffer *render_cb, size_t sem_index, int sync_file_fd) {
	VkResult res;

	VkSemaphore *wait_semaphores = render_cb->wait_semaphores.data;
	size_t wait_semaphores_len = render_cb->wait_semaphores.size / sizeof(wait_semaphores[0]);

	VkSemaphore *sem_ptr;
	if (sem_index >= wait_semaphores_len) {
		sem_ptr = wl_array_add(&render_cb->wait_semaphores, sizeof(*sem_ptr));
		if (sem_ptr == NULL) {
			return VK_NULL_HANDLE;
		}
		*sem_ptr = VK_NULL_HANDLE;
	} else {
		sem_ptr = &wait_semaphores[sem_index];
	}

	if (*sem_ptr == VK_NULL_HANDLE) {
		VkSemaphoreCreateInfo semaphore_info = {
			.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
		};
		res = vkCreateSemaphore(renderer->dev->dev, &semaphore_info, NULL, sem_ptr);
		if (res != VK_SUCCESS) {
			wlr_vk_error("vkCreateSemaphore", res);
			return VK_NULL_HANDLE;
		}
	}

	VkImportSemaphoreFdInfoKHR import_info = {
		.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR,
		.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
		.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT,
		.semaphore = *sem_ptr,
		.fd = sync_file_fd,
	};
	res = renderer->dev->api.vkImportSemaphoreFdKHR(renderer->dev->dev, &import_info);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkImportSemaphoreFdKHR", res);
		return VK_NULL_HANDLE;
	}

	return *sem_ptr;
}

bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer, int wait_sync_file_fd) {
	if (renderer->stage.cb == NULL) {
		return false;
	}

	struct wlr_vk_command_buffer *cb = renderer->stage.cb;
	renderer->stage.cb = NULL;

	uint64_t timeline_point = vulkan_end_command_buffer(cb, renderer);
	if (timeline_point == 0) {
		return false;
	}

	VkSemaphore wait_semaphore;
	VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
	VkTimelineSemaphoreSubmitInfoKHR timeline_submit_info = {
		.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR,
		.signalSemaphoreValueCount = 1,
		.pSignalSemaphoreValues = &timeline_point,
	};
	VkSubmitInfo submit_info = {
		.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
		.pNext = &timeline_submit_info,
		.commandBufferCount = 1,
		.pCommandBuffers = &cb->vk,
		.signalSemaphoreCount = 1,
		.pSignalSemaphores = &renderer->timeline_semaphore,
	};

	if (wait_sync_file_fd != -1) {
		wait_semaphore = vulkan_command_buffer_wait_sync_file(renderer, cb, 0, wait_sync_file_fd);
		if (wait_semaphore == VK_NULL_HANDLE) {
			return false;
		}
		submit_info.waitSemaphoreCount = 1;
		submit_info.pWaitSemaphores = &wait_semaphore;
		submit_info.pWaitDstStageMask = &wait_stage;
	}

	vulkan_stage_mark_submit(renderer, timeline_point);

	VkResult res = vkQueueSubmit(renderer->dev->queue, 1, &submit_info, VK_NULL_HANDLE);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkQueueSubmit", res);
		return false;
	}

	if (!vulkan_wait_command_buffer(cb, renderer)) {
		return false;
	}

	// We did a blocking wait so this is now the current point
	stage_buffer_gc(renderer, timeline_point);
	return true;
}

struct wlr_vk_format_props *vulkan_format_props_from_drm(
		struct wlr_vk_device *dev, uint32_t drm_fmt) {
	for (size_t i = 0u; i < dev->format_prop_count; ++i) {
		if (dev->format_props[i].format.drm == drm_fmt) {
			return &dev->format_props[i];
		}
	}
	return NULL;
}

static bool init_command_buffer(struct wlr_vk_command_buffer *cb,
		struct wlr_vk_renderer *renderer) {
	VkResult res;

	VkCommandBuffer vk_cb = VK_NULL_HANDLE;
	VkCommandBufferAllocateInfo cmd_buf_info = {
		.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
		.commandPool = renderer->command_pool,
		.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
		.commandBufferCount = 1,
	};
	res = vkAllocateCommandBuffers(renderer->dev->dev, &cmd_buf_info, &vk_cb);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkAllocateCommandBuffers", res);
		return false;
	}

	*cb = (struct wlr_vk_command_buffer){
		.vk = vk_cb,
	};
	wl_list_init(&cb->destroy_textures);
	return true;
}

bool vulkan_wait_command_buffer(struct wlr_vk_command_buffer *cb,
		struct wlr_vk_renderer *renderer) {
	VkResult res;

	assert(cb->vk != VK_NULL_HANDLE && !cb->recording);

	VkSemaphoreWaitInfoKHR wait_info = {
		.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO_KHR,
		.semaphoreCount = 1,
		.pSemaphores = &renderer->timeline_semaphore,
		.pValues = &cb->timeline_point,
	};
	res = renderer->dev->api.vkWaitSemaphoresKHR(renderer->dev->dev, &wait_info, UINT64_MAX);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkWaitSemaphoresKHR", res);
		return false;
	}

	return true;
}

static void release_command_buffer_resources(struct wlr_vk_command_buffer *cb,
		struct wlr_vk_renderer *renderer) {
	struct wlr_vk_texture *texture, *texture_tmp;
	wl_list_for_each_safe(texture, texture_tmp, &cb->destroy_textures, destroy_link) {
		wl_list_remove(&texture->destroy_link);
		texture->last_used_cb = NULL;
		wlr_texture_destroy(&texture->wlr_texture);
	}

	if (cb->color_transform) {
		wlr_color_transform_unref(cb->color_transform);
		cb->color_transform = NULL;
	}
}

static struct wlr_vk_command_buffer *get_command_buffer(
		struct wlr_vk_renderer *renderer) {
	VkResult res;

	uint64_t current_point;
	res = renderer->dev->api.vkGetSemaphoreCounterValueKHR(renderer->dev->dev,
		renderer->timeline_semaphore, &current_point);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkGetSemaphoreCounterValueKHR", res);
		return NULL;
	}

	stage_buffer_gc(renderer, current_point);

	// Destroy textures for completed command buffers
	for (size_t i = 0; i < VULKAN_COMMAND_BUFFERS_CAP; i++) {
		struct wlr_vk_command_buffer *cb = &renderer->command_buffers[i];
		if (cb->vk != VK_NULL_HANDLE && !cb->recording &&
				cb->timeline_point <= current_point) {
			release_command_buffer_resources(cb, renderer);
		}
	}

	// First try to find an existing command buffer which isn't busy
	struct wlr_vk_command_buffer *unused = NULL;
	struct wlr_vk_command_buffer *wait = NULL;
	for (size_t i = 0; i < VULKAN_COMMAND_BUFFERS_CAP; i++) {
		struct wlr_vk_command_buffer *cb = &renderer->command_buffers[i];
		if (cb->vk == VK_NULL_HANDLE) {
			unused = cb;
			break;
		}
		if (cb->recording) {
			continue;
		}

		if (cb->timeline_point <= current_point) {
			return cb;
		}
		if (wait == NULL || cb->timeline_point < wait->timeline_point) {
			wait = cb;
		}
	}

	// If there is an unused slot, initialize it
	if (unused != NULL) {
		if (!init_command_buffer(unused, renderer)) {
			return NULL;
		}
		return unused;
	}

	// Block until a busy command buffer becomes available
	if (!vulkan_wait_command_buffer(wait, renderer)) {
		return NULL;
	}
	return wait;
}

struct wlr_vk_command_buffer *vulkan_acquire_command_buffer(
		struct wlr_vk_renderer *renderer) {
	struct wlr_vk_command_buffer *cb = get_command_buffer(renderer);
	if (cb == NULL) {
		return NULL;
	}

	assert(!cb->recording);
	cb->recording = true;

	return cb;
}

uint64_t vulkan_end_command_buffer(struct wlr_vk_command_buffer *cb,
		struct wlr_vk_renderer *renderer) {
	assert(cb->recording);
	cb->recording = false;

	VkResult res = vkEndCommandBuffer(cb->vk);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkEndCommandBuffer", res);
		return 0;
	}

	renderer->timeline_point++;
	cb->timeline_point = renderer->timeline_point;
	return cb->timeline_point;
}

void vulkan_reset_command_buffer(struct wlr_vk_command_buffer *cb) {
	if (cb == NULL) {
		return;
	}

	cb->recording = false;

	VkResult res = vkResetCommandBuffer(cb->vk, 0);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkResetCommandBuffer", res);
	}
}

static void finish_render_buffer_out(struct wlr_vk_render_buffer_out *out,
		VkDevice dev) {
	vkDestroyFramebuffer(dev, out->framebuffer, NULL);
	vkDestroyImageView(dev, out->image_view, NULL);
}

static void destroy_render_buffer(struct wlr_vk_render_buffer *buffer) {
	wl_list_remove(&buffer->link);
	wlr_addon_finish(&buffer->addon);

	VkDevice dev = buffer->renderer->dev->dev;

	// TODO: asynchronously wait for the command buffers using this render
	// buffer to complete (just like we do for textures)
	VkResult res = vkQueueWaitIdle(buffer->renderer->dev->queue);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkQueueWaitIdle", res);
	}

	finish_render_buffer_out(&buffer->linear.out, dev);
	finish_render_buffer_out(&buffer->srgb.out, dev);

	finish_render_buffer_out(&buffer->two_pass.out, dev);
	vkDestroyImage(dev, buffer->two_pass.blend_image, NULL);
	vkFreeMemory(dev, buffer->two_pass.blend_memory, NULL);
	vkDestroyImageView(dev, buffer->two_pass.blend_image_view, NULL);
	if (buffer->two_pass.blend_attachment_pool) {
		vulkan_free_ds(buffer->renderer, buffer->two_pass.blend_attachment_pool,
			buffer->two_pass.blend_descriptor_set);
	}

	vkDestroyImage(dev, buffer->image, NULL);
	for (size_t i = 0u; i < buffer->mem_count; ++i) {
		vkFreeMemory(dev, buffer->memories[i], NULL);
	}

	free(buffer);
}

static void handle_render_buffer_destroy(struct wlr_addon *addon) {
	struct wlr_vk_render_buffer *buffer = wl_container_of(addon, buffer, addon);
	destroy_render_buffer(buffer);
}

static struct wlr_addon_interface render_buffer_addon_impl = {
	.name = "wlr_vk_render_buffer",
	.destroy = handle_render_buffer_destroy,
};

bool vulkan_setup_two_pass_framebuffer(struct wlr_vk_render_buffer *buffer,
		const struct wlr_dmabuf_attributes *dmabuf) {
	struct wlr_vk_renderer *renderer = buffer->renderer;
	VkResult res;
	VkDevice dev = renderer->dev->dev;

	const struct wlr_vk_format_props *fmt = vulkan_format_props_from_drm(
		renderer->dev, dmabuf->format);
	assert(fmt);

	VkImageViewCreateInfo view_info = {
		.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
		.image = buffer->image,
		.viewType = VK_IMAGE_VIEW_TYPE_2D,
		.format = fmt->format.vk,
		.components.r = VK_COMPONENT_SWIZZLE_IDENTITY,
		.components.g = VK_COMPONENT_SWIZZLE_IDENTITY,
		.components.b = VK_COMPONENT_SWIZZLE_IDENTITY,
		.components.a = VK_COMPONENT_SWIZZLE_IDENTITY,
		.subresourceRange = (VkImageSubresourceRange) {
			.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
			.baseMipLevel = 0,
			.levelCount = 1,
			.baseArrayLayer = 0,
			.layerCount = 1,
		},
	};

	res = vkCreateImageView(dev, &view_info, NULL, &buffer->two_pass.out.image_view);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateImageView failed", res);
		goto error;
	}

	buffer->two_pass.render_setup = find_or_create_render_setup(
		renderer, &fmt->format, true, false);
	if (!buffer->two_pass.render_setup) {
		goto error;
	}

	// Set up an extra 16F buffer on which to do linear blending,
	// and afterwards to render onto the target
	VkImageCreateInfo img_info = {
		.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
		.imageType = VK_IMAGE_TYPE_2D,
		.format = VK_FORMAT_R16G16B16A16_SFLOAT,
		.mipLevels = 1,
		.arrayLayers = 1,
		.samples = VK_SAMPLE_COUNT_1_BIT,
		.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
		.tiling = VK_IMAGE_TILING_OPTIMAL,
		.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
		.extent = (VkExtent3D) { dmabuf->width, dmabuf->height, 1 },
		.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT,
	};

	res = vkCreateImage(dev, &img_info, NULL, &buffer->two_pass.blend_image);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateImage failed", res);
		goto error;
	}

	VkMemoryRequirements mem_reqs;
	vkGetImageMemoryRequirements(dev, buffer->two_pass.blend_image, &mem_reqs);

	int mem_type_index = vulkan_find_mem_type(renderer->dev,
		VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mem_reqs.memoryTypeBits);
	if (mem_type_index == -1) {
		wlr_log(WLR_ERROR, "failed to find suitable vulkan memory type");
		goto error;
	}

	VkMemoryAllocateInfo mem_info = {
		.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
		.allocationSize = mem_reqs.size,
		.memoryTypeIndex = mem_type_index,
	};

	res = vkAllocateMemory(dev, &mem_info, NULL, &buffer->two_pass.blend_memory);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkAllocatorMemory failed", res);
		goto error;
	}

	res = vkBindImageMemory(dev, buffer->two_pass.blend_image, buffer->two_pass.blend_memory, 0);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkBindMemory failed", res);
		goto error;
	}

	VkImageViewCreateInfo blend_view_info = {
		.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
		.image = buffer->two_pass.blend_image,
		.viewType = VK_IMAGE_VIEW_TYPE_2D,
		.format = img_info.format,
		.components.r = VK_COMPONENT_SWIZZLE_IDENTITY,
		.components.g = VK_COMPONENT_SWIZZLE_IDENTITY,
		.components.b = VK_COMPONENT_SWIZZLE_IDENTITY,
		.components.a = VK_COMPONENT_SWIZZLE_IDENTITY,
		.subresourceRange = (VkImageSubresourceRange) {
			.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
			.baseMipLevel = 0,
			.levelCount = 1,
			.baseArrayLayer = 0,
			.layerCount = 1,
		},
	};

	res = vkCreateImageView(dev, &blend_view_info, NULL, &buffer->two_pass.blend_image_view);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateImageView failed", res);
		goto error;
	}

	buffer->two_pass.blend_attachment_pool = vulkan_alloc_blend_ds(renderer,
		&buffer->two_pass.blend_descriptor_set);
	if (!buffer->two_pass.blend_attachment_pool) {
		wlr_log(WLR_ERROR, "failed to allocate descriptor");
		goto error;
	}

	VkDescriptorImageInfo ds_attach_info = {
		.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
		.imageView = buffer->two_pass.blend_image_view,
		.sampler = VK_NULL_HANDLE,
	};
	VkWriteDescriptorSet ds_write = {
		.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
		.descriptorCount = 1,
		.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
		.dstSet = buffer->two_pass.blend_descriptor_set,
		.dstBinding = 0,
		.pImageInfo = &ds_attach_info,
	};
	vkUpdateDescriptorSets(dev, 1, &ds_write, 0, NULL);

	VkImageView attachments[] = {
		buffer->two_pass.blend_image_view,
		buffer->two_pass.out.image_view,
	};
	VkFramebufferCreateInfo fb_info = {
		.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
		.attachmentCount = sizeof(attachments) / sizeof(attachments[0]),
		.pAttachments = attachments,
		.flags = 0u,
		.width = dmabuf->width,
		.height = dmabuf->height,
		.layers = 1u,
		.renderPass = buffer->two_pass.render_setup->render_pass,
	};

	res = vkCreateFramebuffer(dev, &fb_info, NULL, &buffer->two_pass.out.framebuffer);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateFramebuffer", res);
		goto error;
	}

	return true;

error:
	// cleaning up everything is the caller's responsibility,
	// since it will need to do this anyway if framebuffer setup fails
	return false;
}

bool vulkan_setup_one_pass_framebuffer(struct wlr_vk_render_buffer *buffer,
		const struct wlr_dmabuf_attributes *dmabuf, bool srgb) {
	struct wlr_vk_renderer *renderer = buffer->renderer;
	VkResult res;
	VkDevice dev = renderer->dev->dev;

	const struct wlr_vk_format_props *fmt = vulkan_format_props_from_drm(
		renderer->dev, dmabuf->format);
	assert(fmt);

	VkFormat vk_fmt = srgb ? fmt->format.vk_srgb : fmt->format.vk;
	assert(vk_fmt != VK_FORMAT_UNDEFINED);

	struct wlr_vk_render_buffer_out *out = srgb ? &buffer->srgb.out : &buffer->linear.out;

	// Set up the srgb framebuffer by default; two-pass framebuffer and
	// blending image will be set up later if necessary
	VkImageViewCreateInfo view_info = {
		.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
		.image = buffer->image,
		.viewType = VK_IMAGE_VIEW_TYPE_2D,
		.format = vk_fmt,
		.components.r = VK_COMPONENT_SWIZZLE_IDENTITY,
		.components.g = VK_COMPONENT_SWIZZLE_IDENTITY,
		.components.b = VK_COMPONENT_SWIZZLE_IDENTITY,
		.components.a = VK_COMPONENT_SWIZZLE_IDENTITY,
		.subresourceRange = (VkImageSubresourceRange) {
			.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
			.baseMipLevel = 0,
			.levelCount = 1,
			.baseArrayLayer = 0,
			.layerCount = 1,
		},
	};

	res = vkCreateImageView(dev, &view_info, NULL, &out->image_view);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateImageView failed", res);
		goto error;
	}

	struct wlr_vk_render_format_setup *render_setup =
		find_or_create_render_setup(renderer, &fmt->format, false, srgb);
	if (!render_setup) {
		goto error;
	}

	VkFramebufferCreateInfo fb_info = {
		.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
		.attachmentCount = 1,
		.pAttachments = &out->image_view,
		.flags = 0u,
		.width = dmabuf->width,
		.height = dmabuf->height,
		.layers = 1u,
		.renderPass = render_setup->render_pass,
	};

	res = vkCreateFramebuffer(dev, &fb_info, NULL, &out->framebuffer);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateFramebuffer", res);
		goto error;
	}

	if (srgb) {
		buffer->srgb.render_setup = render_setup;
	} else {
		buffer->linear.render_setup = render_setup;
	}

	return true;

error:
	// cleaning up everything is the caller's responsibility,
	// since it will need to do this anyway if framebuffer setup fails
	return false;
}

static struct wlr_vk_render_buffer *create_render_buffer(
		struct wlr_vk_renderer *renderer, struct wlr_buffer *wlr_buffer) {
	struct wlr_vk_render_buffer *buffer = calloc(1, sizeof(*buffer));
	if (buffer == NULL) {
		wlr_log_errno(WLR_ERROR, "Allocation failed");
		return NULL;
	}
	buffer->wlr_buffer = wlr_buffer;
	buffer->renderer = renderer;
	wlr_addon_init(&buffer->addon, &wlr_buffer->addons, renderer,
		&render_buffer_addon_impl);
	wl_list_insert(&renderer->render_buffers, &buffer->link);

	struct wlr_dmabuf_attributes dmabuf = {0};
	if (!wlr_buffer_get_dmabuf(wlr_buffer, &dmabuf)) {
		goto error;
	}

	wlr_log(WLR_DEBUG, "vulkan create_render_buffer: %.4s, %dx%d",
		(const char*) &dmabuf.format, dmabuf.width, dmabuf.height);

	bool using_mutable_srgb = false;
	buffer->image = vulkan_import_dmabuf(renderer, &dmabuf,
		buffer->memories, &buffer->mem_count, true, &using_mutable_srgb);
	if (!buffer->image) {
		goto error;
	}

	const struct wlr_vk_format_props *fmt = vulkan_format_props_from_drm(
		renderer->dev, dmabuf.format);
	if (fmt == NULL) {
		wlr_log(WLR_ERROR, "Unsupported pixel format %"PRIx32 " (%.4s)",
			dmabuf.format, (const char*) &dmabuf.format);
		goto error;
	}

	if (using_mutable_srgb) {
		if (!vulkan_setup_one_pass_framebuffer(buffer, &dmabuf, true)) {
			goto error;
		}
	} else {
		// Set up the two-pass framebuffer & blending image
		if (!vulkan_setup_two_pass_framebuffer(buffer, &dmabuf)) {
			goto error;
		}
	}

	return buffer;

error:
	if (buffer) {
		destroy_render_buffer(buffer);
	}

	wlr_dmabuf_attributes_finish(&dmabuf);
	return NULL;
}

static struct wlr_vk_render_buffer *get_render_buffer(
		struct wlr_vk_renderer *renderer, struct wlr_buffer *wlr_buffer) {
	struct wlr_addon *addon =
		wlr_addon_find(&wlr_buffer->addons, renderer, &render_buffer_addon_impl);
	if (addon == NULL) {
		return NULL;
	}

	struct wlr_vk_render_buffer *buffer = wl_container_of(addon, buffer, addon);
	return buffer;
}

static bool buffer_export_sync_file(struct wlr_vk_renderer *renderer, struct wlr_buffer *buffer,
		uint32_t flags, int sync_file_fds[static WLR_DMABUF_MAX_PLANES]) {
	struct wlr_dmabuf_attributes dmabuf = {0};
	if (!wlr_buffer_get_dmabuf(buffer, &dmabuf)) {
		wlr_log(WLR_ERROR, "wlr_buffer_get_dmabuf() failed");
		return false;
	}

	if (!renderer->dev->implicit_sync_interop) {
		// We have no choice but to block here sadly

		for (int i = 0; i < dmabuf.n_planes; i++) {
			struct pollfd pollfd = {
				.fd = dmabuf.fd[i],
				.events = (flags & DMA_BUF_SYNC_WRITE) ? POLLOUT : POLLIN,
			};
			int timeout_ms = 1000;
			int ret = poll(&pollfd, 1, timeout_ms);
			if (ret < 0) {
				wlr_log_errno(WLR_ERROR, "Failed to wait for DMA-BUF fence");
				return false;
			} else if (ret == 0) {
				wlr_log(WLR_ERROR, "Timed out while waiting for DMA-BUF fence");
				return false;
			}
		}

		return true;
	}

	for (int i = 0; i < dmabuf.n_planes; i++) {
		int sync_file_fd = dmabuf_export_sync_file(dmabuf.fd[i], flags);
		if (sync_file_fd < 0) {
			wlr_log(WLR_ERROR, "Failed to extract DMA-BUF fence");
			return false;
		}

		sync_file_fds[i] = sync_file_fd;
	}

	return true;
}

bool vulkan_sync_foreign_texture_acquire(struct wlr_vk_texture *texture,
		int sync_file_fds[static WLR_DMABUF_MAX_PLANES]) {
	return buffer_export_sync_file(texture->renderer, texture->buffer, DMA_BUF_SYNC_READ, sync_file_fds);
}

bool vulkan_sync_render_buffer_acquire(struct wlr_vk_render_buffer *render_buffer,
		int sync_file_fds[static WLR_DMABUF_MAX_PLANES]) {
	return buffer_export_sync_file(render_buffer->renderer, render_buffer->wlr_buffer,
		DMA_BUF_SYNC_WRITE, sync_file_fds);
}

static bool buffer_import_sync_file(struct wlr_buffer *buffer, uint32_t flags, int sync_file_fd) {
	struct wlr_dmabuf_attributes dmabuf = {0};
	if (!wlr_buffer_get_dmabuf(buffer, &dmabuf)) {
		wlr_log(WLR_ERROR, "wlr_buffer_get_dmabuf() failed");
		return false;
	}

	for (int i = 0; i < dmabuf.n_planes; i++) {
		if (!dmabuf_import_sync_file(dmabuf.fd[i], flags,
				sync_file_fd)) {
			return false;
		}
	}

	return true;
}

bool vulkan_sync_render_pass_release(struct wlr_vk_renderer *renderer,
		struct wlr_vk_render_pass *pass) {
	VkResult res;
	struct wlr_vk_command_buffer *cb = pass->command_buffer;

	if (!renderer->dev->implicit_sync_interop && pass->signal_timeline == NULL) {
		// We have no choice but to block here sadly
		return vulkan_wait_command_buffer(cb, renderer);
	}

	assert(cb->binary_semaphore != VK_NULL_HANDLE);

	// Note: vkGetSemaphoreFdKHR implicitly resets the semaphore
	const VkSemaphoreGetFdInfoKHR get_fence_fd_info = {
		.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR,
		.semaphore = cb->binary_semaphore,
		.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
	};
	int sync_file_fd = -1;
	res = renderer->dev->api.vkGetSemaphoreFdKHR(renderer->dev->dev,
		&get_fence_fd_info, &sync_file_fd);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkGetSemaphoreFdKHR", res);
		return false;
	}

	bool ok = false;
	if (pass->signal_timeline != NULL) {
		if (!wlr_drm_syncobj_timeline_import_sync_file(pass->signal_timeline,
				pass->signal_point, sync_file_fd)) {
			goto out;
		}
	} else {
		if (!buffer_import_sync_file(pass->render_buffer->wlr_buffer, DMA_BUF_SYNC_WRITE, sync_file_fd)) {
			goto out;
		}

		struct wlr_vk_render_pass_texture *pass_texture;
		wl_array_for_each(pass_texture, &pass->textures) {
			if (!buffer_import_sync_file(pass_texture->texture->buffer, DMA_BUF_SYNC_READ, sync_file_fd)) {
				goto out;
			}
		}
	}

	ok = true;

out:
	close(sync_file_fd);
	return ok;
}

static const struct wlr_drm_format_set *vulkan_get_texture_formats(
		struct wlr_renderer *wlr_renderer, uint32_t buffer_caps) {
	struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer);
	if (buffer_caps & WLR_BUFFER_CAP_DMABUF) {
		return &renderer->dev->dmabuf_texture_formats;
	} else if (buffer_caps & WLR_BUFFER_CAP_DATA_PTR) {
		return &renderer->dev->shm_texture_formats;
	} else {
		return NULL;
	}
}

static const struct wlr_drm_format_set *vulkan_get_render_formats(
		struct wlr_renderer *wlr_renderer) {
	struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer);
	return &renderer->dev->dmabuf_render_formats;
}

static void vulkan_destroy(struct wlr_renderer *wlr_renderer) {
	struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer);
	struct wlr_vk_device *dev = renderer->dev;
	if (!dev) {
		free(renderer);
		return;
	}

	VkResult res = vkDeviceWaitIdle(renderer->dev->dev);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkDeviceWaitIdle", res);
	}

	for (size_t i = 0; i < VULKAN_COMMAND_BUFFERS_CAP; i++) {
		struct wlr_vk_command_buffer *cb = &renderer->command_buffers[i];
		if (cb->vk == VK_NULL_HANDLE) {
			continue;
		}
		release_command_buffer_resources(cb, renderer);
		if (cb->binary_semaphore != VK_NULL_HANDLE) {
			vkDestroySemaphore(renderer->dev->dev, cb->binary_semaphore, NULL);
		}
		VkSemaphore *sem_ptr;
		wl_array_for_each(sem_ptr, &cb->wait_semaphores) {
			vkDestroySemaphore(renderer->dev->dev, *sem_ptr, NULL);
		}
		wl_array_release(&cb->wait_semaphores);
	}

	// stage.cb automatically freed with command pool
	struct wlr_vk_stage_buffer *buf, *tmp_buf;
	wl_list_for_each_safe(buf, tmp_buf, &renderer->stage.buffers, link) {
		stage_buffer_destroy(renderer, buf);
	}

	struct wlr_vk_texture *tex, *tex_tmp;
	wl_list_for_each_safe(tex, tex_tmp, &renderer->textures, link) {
		vulkan_texture_destroy(tex);
	}

	struct wlr_vk_render_buffer *render_buffer, *render_buffer_tmp;
	wl_list_for_each_safe(render_buffer, render_buffer_tmp,
			&renderer->render_buffers, link) {
		destroy_render_buffer(render_buffer);
	}

	struct wlr_vk_color_transform *color_transform, *color_transform_tmp;
	wl_list_for_each_safe(color_transform, color_transform_tmp,
			&renderer->color_transforms, link) {
		vk_color_transform_destroy(&color_transform->addon);
	}

	struct wlr_vk_render_format_setup *setup, *tmp_setup;
	wl_list_for_each_safe(setup, tmp_setup,
			&renderer->render_format_setups, link) {
		destroy_render_format_setup(renderer, setup);
	}

	struct wlr_vk_descriptor_pool *pool, *tmp_pool;
	wl_list_for_each_safe(pool, tmp_pool, &renderer->descriptor_pools, link) {
		vkDestroyDescriptorPool(dev->dev, pool->pool, NULL);
		free(pool);
	}
	wl_list_for_each_safe(pool, tmp_pool, &renderer->output_descriptor_pools, link) {
		vkDestroyDescriptorPool(dev->dev, pool->pool, NULL);
		free(pool);
	}

	vkDestroyShaderModule(dev->dev, renderer->vert_module, NULL);
	vkDestroyShaderModule(dev->dev, renderer->tex_frag_module, NULL);
	vkDestroyShaderModule(dev->dev, renderer->quad_frag_module, NULL);
	vkDestroyShaderModule(dev->dev, renderer->output_module, NULL);

	struct wlr_vk_pipeline_layout *pipeline_layout, *pipeline_layout_tmp;
	wl_list_for_each_safe(pipeline_layout, pipeline_layout_tmp,
			&renderer->pipeline_layouts, link) {
		vkDestroyPipelineLayout(dev->dev, pipeline_layout->vk, NULL);
		vkDestroyDescriptorSetLayout(dev->dev, pipeline_layout->ds, NULL);
		vkDestroySampler(dev->dev, pipeline_layout->sampler, NULL);
		renderer->dev->api.vkDestroySamplerYcbcrConversionKHR(dev->dev, pipeline_layout->ycbcr.conversion, NULL);
		free(pipeline_layout);
	}

	vkDestroyImageView(dev->dev, renderer->dummy3d_image_view, NULL);
	vkDestroyImage(dev->dev, renderer->dummy3d_image, NULL);
	vkFreeMemory(dev->dev, renderer->dummy3d_mem, NULL);

	vkDestroySemaphore(dev->dev, renderer->timeline_semaphore, NULL);
	vkDestroyPipelineLayout(dev->dev, renderer->output_pipe_layout, NULL);
	vkDestroyDescriptorSetLayout(dev->dev, renderer->output_ds_srgb_layout, NULL);
	vkDestroyDescriptorSetLayout(dev->dev, renderer->output_ds_lut3d_layout, NULL);
	vkDestroyCommandPool(dev->dev, renderer->command_pool, NULL);
	vkDestroySampler(dev->dev, renderer->output_sampler_lut3d, NULL);

	if (renderer->read_pixels_cache.initialized) {
		vkFreeMemory(dev->dev, renderer->read_pixels_cache.dst_img_memory, NULL);
		vkDestroyImage(dev->dev, renderer->read_pixels_cache.dst_image, NULL);
	}

	struct wlr_vk_instance *ini = dev->instance;
	vulkan_device_destroy(dev);
	vulkan_instance_destroy(ini);
	free(renderer);
}

bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer,
		VkFormat src_format, VkImage src_image,
		uint32_t drm_format, uint32_t stride,
		uint32_t width, uint32_t height, uint32_t src_x, uint32_t src_y,
		uint32_t dst_x, uint32_t dst_y, void *data,
		struct wlr_drm_syncobj_timeline *wait_timeline, uint64_t wait_point) {
	VkDevice dev = vk_renderer->dev->dev;

	const struct wlr_pixel_format_info *pixel_format_info = drm_get_pixel_format_info(drm_format);
	if (!pixel_format_info) {
		wlr_log(WLR_ERROR, "vulkan_read_pixels: could not find pixel format info "
				"for DRM format 0x%08x", drm_format);
		return false;
	} else if (pixel_format_info_pixels_per_block(pixel_format_info) != 1) {
		wlr_log(WLR_ERROR, "vulkan_read_pixels: block formats are not supported");
		return false;
	}

	const struct wlr_vk_format *wlr_vk_format = vulkan_get_format_from_drm(drm_format);
	if (!wlr_vk_format) {
		wlr_log(WLR_ERROR, "vulkan_read_pixels: no vulkan format "
				"matching drm format 0x%08x available", drm_format);
		return false;
	}
	VkFormat dst_format = wlr_vk_format->vk;
	VkFormatProperties dst_format_props = {0}, src_format_props = {0};
	vkGetPhysicalDeviceFormatProperties(vk_renderer->dev->phdev, dst_format, &dst_format_props);
	vkGetPhysicalDeviceFormatProperties(vk_renderer->dev->phdev, src_format, &src_format_props);

	bool blit_supported = src_format_props.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT &&
		dst_format_props.linearTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT;
	if (!blit_supported && src_format != dst_format) {
		wlr_log(WLR_ERROR, "vulkan_read_pixels: blit unsupported and no manual "
					"conversion available from src to dst format.");
		return false;
	}

	VkResult res;
	VkImage dst_image;
	VkDeviceMemory dst_img_memory;
	bool use_cached = vk_renderer->read_pixels_cache.initialized &&
		vk_renderer->read_pixels_cache.drm_format == drm_format &&
		vk_renderer->read_pixels_cache.width == width &&
		vk_renderer->read_pixels_cache.height == height;

	if (use_cached) {
		dst_image = vk_renderer->read_pixels_cache.dst_image;
		dst_img_memory = vk_renderer->read_pixels_cache.dst_img_memory;
	} else {
		VkImageCreateInfo image_create_info = {
			.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
			.imageType = VK_IMAGE_TYPE_2D,
			.format = dst_format,
			.extent.width = width,
			.extent.height = height,
			.extent.depth = 1,
			.arrayLayers = 1,
			.mipLevels = 1,
			.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
			.samples = VK_SAMPLE_COUNT_1_BIT,
			.tiling = VK_IMAGE_TILING_LINEAR,
			.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT
		};
		res = vkCreateImage(dev, &image_create_info, NULL, &dst_image);
		if (res != VK_SUCCESS) {
			wlr_vk_error("vkCreateImage", res);
			return false;
		}

		VkMemoryRequirements mem_reqs;
		vkGetImageMemoryRequirements(dev, dst_image, &mem_reqs);

		int mem_type = vulkan_find_mem_type(vk_renderer->dev,
				VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
				VK_MEMORY_PROPERTY_HOST_CACHED_BIT,
				mem_reqs.memoryTypeBits);
		if (mem_type < 0) {
			wlr_log(WLR_ERROR, "vulkan_read_pixels: could not find adequate memory type");
			goto destroy_image;
		}

		VkMemoryAllocateInfo mem_alloc_info = {
			.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
		};
		mem_alloc_info.allocationSize = mem_reqs.size;
		mem_alloc_info.memoryTypeIndex = mem_type;

		res = vkAllocateMemory(dev, &mem_alloc_info, NULL, &dst_img_memory);
		if (res != VK_SUCCESS) {
			wlr_vk_error("vkAllocateMemory", res);
			goto destroy_image;
		}
		res = vkBindImageMemory(dev, dst_image, dst_img_memory, 0);
		if (res != VK_SUCCESS) {
			wlr_vk_error("vkBindImageMemory", res);
			goto free_memory;
		}

		if (vk_renderer->read_pixels_cache.initialized) {
			vkFreeMemory(dev, vk_renderer->read_pixels_cache.dst_img_memory, NULL);
			vkDestroyImage(dev, vk_renderer->read_pixels_cache.dst_image, NULL);
		}
		vk_renderer->read_pixels_cache.initialized = true;
		vk_renderer->read_pixels_cache.drm_format = drm_format;
		vk_renderer->read_pixels_cache.dst_image = dst_image;
		vk_renderer->read_pixels_cache.dst_img_memory = dst_img_memory;
		vk_renderer->read_pixels_cache.width = width;
		vk_renderer->read_pixels_cache.height = height;
	}

	VkCommandBuffer cb = vulkan_record_stage_cb(vk_renderer);
	if (cb == VK_NULL_HANDLE) {
		return false;
	}

	vulkan_change_layout(cb, dst_image,
			VK_IMAGE_LAYOUT_UNDEFINED,
			VK_PIPELINE_STAGE_TRANSFER_BIT,
			0,
			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
			VK_PIPELINE_STAGE_TRANSFER_BIT,
			VK_ACCESS_TRANSFER_WRITE_BIT);
	vulkan_change_layout(cb, src_image,
			VK_IMAGE_LAYOUT_GENERAL,
			VK_PIPELINE_STAGE_TRANSFER_BIT,
			VK_ACCESS_MEMORY_READ_BIT,
			VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
			VK_PIPELINE_STAGE_TRANSFER_BIT,
			VK_ACCESS_TRANSFER_READ_BIT);

	if (blit_supported) {
		VkImageBlit image_blit_region = {
			.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
			.srcSubresource.layerCount = 1,
			.srcOffsets[0] = {
				.x = src_x,
				.y = src_y,
				.z = 0,
			},
			.srcOffsets[1] = {
				.x = src_x + width,
				.y = src_y + height,
				.z = 1,
			},
			.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
			.dstSubresource.layerCount = 1,
			.dstOffsets[1] = {
				.x = width,
				.y = height,
				.z = 1,
			}
		};
		vkCmdBlitImage(cb, src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
				dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
				1, &image_blit_region, VK_FILTER_NEAREST);
	} else {
		wlr_log(WLR_DEBUG, "vulkan_read_pixels: blit unsupported, falling back to vkCmdCopyImage.");
		VkImageCopy image_region = {
			.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
			.srcSubresource.layerCount = 1,
			.srcOffset = {
				.x = src_x,
				.y = src_y,
			},
			.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
			.dstSubresource.layerCount = 1,
			.extent = {
				.width = width,
				.height = height,
				.depth = 1,
			}
		};
		vkCmdCopyImage(cb, src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
				dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_region);
	}

	vulkan_change_layout(cb, dst_image,
			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
			VK_PIPELINE_STAGE_TRANSFER_BIT,
			VK_ACCESS_TRANSFER_WRITE_BIT,
			VK_IMAGE_LAYOUT_GENERAL,
			VK_PIPELINE_STAGE_TRANSFER_BIT,
			0);
	vulkan_change_layout(cb, src_image,
			VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
			VK_PIPELINE_STAGE_TRANSFER_BIT,
			VK_ACCESS_TRANSFER_READ_BIT,
			VK_IMAGE_LAYOUT_GENERAL,
			VK_PIPELINE_STAGE_TRANSFER_BIT,
			VK_ACCESS_MEMORY_READ_BIT);

	int wait_sync_file_fd = -1;
	if (wait_timeline != NULL) {
		wait_sync_file_fd = wlr_drm_syncobj_timeline_export_sync_file(wait_timeline, wait_point);
		if (wait_sync_file_fd < 0) {
			wlr_log(WLR_ERROR, "Failed to export wait timeline point as sync_file");
			return false;
		}
	}

	if (!vulkan_submit_stage_wait(vk_renderer, wait_sync_file_fd)) {
		close(wait_sync_file_fd);
		return false;
	}

	VkImageSubresource img_sub_res = {
		.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
		.arrayLayer = 0,
		.mipLevel = 0
	};
	VkSubresourceLayout img_sub_layout;
	vkGetImageSubresourceLayout(dev, dst_image, &img_sub_res, &img_sub_layout);

	void *v;
	res = vkMapMemory(dev, dst_img_memory, 0, VK_WHOLE_SIZE, 0, &v);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkMapMemory", res);
		return false;
	}

	VkMappedMemoryRange mem_range = {
		.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
		.memory = dst_img_memory,
		.offset = 0,
		.size = VK_WHOLE_SIZE,
	};
	res = vkInvalidateMappedMemoryRanges(dev, 1, &mem_range);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkInvalidateMappedMemoryRanges", res);
		vkUnmapMemory(dev, dst_img_memory);
		return false;
	}

	const char *d = (const char *)v + img_sub_layout.offset;
	unsigned char *p = (unsigned char *)data + dst_y * stride;
	uint32_t bytes_per_pixel = pixel_format_info->bytes_per_block;
	uint32_t pack_stride = img_sub_layout.rowPitch;
	if (pack_stride == stride && dst_x == 0) {
		memcpy(p, d, height * stride);
	} else {
		for (size_t i = 0; i < height; ++i) {
			memcpy(p + i * stride + dst_x * bytes_per_pixel, d + i * pack_stride, width * bytes_per_pixel);
		}
	}

	vkUnmapMemory(dev, dst_img_memory);
	// Don't need to free anything else, since memory and image are cached
	return true;

free_memory:
	vkFreeMemory(dev, dst_img_memory, NULL);
destroy_image:
	vkDestroyImage(dev, dst_image, NULL);

	return false;
}

static int vulkan_get_drm_fd(struct wlr_renderer *wlr_renderer) {
	struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer);
	return renderer->dev->drm_fd;
}

static struct wlr_render_pass *vulkan_begin_buffer_pass(struct wlr_renderer *wlr_renderer,
		struct wlr_buffer *buffer, const struct wlr_buffer_pass_options *options) {
	struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer);

	struct wlr_vk_render_buffer *render_buffer = get_render_buffer(renderer, buffer);
	if (!render_buffer) {
		render_buffer = create_render_buffer(renderer, buffer);
		if (!render_buffer) {
			return NULL;
		}
	}

	struct wlr_vk_render_pass *render_pass = vulkan_begin_render_pass(
		renderer, render_buffer, options);
	if (render_pass == NULL) {
		return NULL;
	}
	return &render_pass->base;
}

static const struct wlr_render_timer_impl render_timer_impl;

static struct wlr_render_timer *vulkan_render_timer_create(
		struct wlr_renderer *wlr_renderer) {
	struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer);
	if (renderer->dev->timestamp_valid_bits == 0) {
		wlr_log(WLR_ERROR, "Failed to create render timer: "
			"timestamp queries not supported by queue family");
		return NULL;
	}

	struct wlr_vk_render_timer *timer = calloc(1, sizeof(*timer));
	if (!timer) {
		wlr_log_errno(WLR_ERROR, "Allocation failed");
		return NULL;
	}

	VkQueryPoolCreateInfo pool_info = {
		.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
		.queryType = VK_QUERY_TYPE_TIMESTAMP,
		.queryCount = 2,
	};
	VkResult res = vkCreateQueryPool(renderer->dev->dev, &pool_info,
		NULL, &timer->query_pool);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateQueryPool", res);
		free(timer);
		return NULL;
	}

	timer->base.impl = &render_timer_impl;
	timer->renderer = renderer;
	return &timer->base;
}

static int vulkan_render_timer_get_duration_ns(
		struct wlr_render_timer *wlr_timer) {
	struct wlr_vk_render_timer *timer =
		wl_container_of(wlr_timer, timer, base);
	struct wlr_vk_renderer *renderer = timer->renderer;

	// Layout: [ timestamp1, avail1, timestamp2, avail2 ]
	uint64_t data[4] = {0};
	VkResult res = vkGetQueryPoolResults(renderer->dev->dev,
		timer->query_pool, 0, 2, sizeof(data), data,
		2 * sizeof(uint64_t),
		VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT);
	if (res == VK_NOT_READY || data[1] == 0 || data[3] == 0) {
		wlr_log(WLR_ERROR, "Failed to get render duration: "
			"timestamp query results not yet ready");
		return -1;
	}
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkGetQueryPoolResults", res);
		return -1;
	}

	uint64_t ticks = data[2] - data[0];
	return (int)(ticks * renderer->dev->timestamp_period);
}

static void vulkan_render_timer_destroy(
		struct wlr_render_timer *wlr_timer) {
	struct wlr_vk_render_timer *timer =
		wl_container_of(wlr_timer, timer, base);
	vkDestroyQueryPool(timer->renderer->dev->dev, timer->query_pool, NULL);
	free(timer);
}

static const struct wlr_render_timer_impl render_timer_impl = {
	.get_duration_ns = vulkan_render_timer_get_duration_ns,
	.destroy = vulkan_render_timer_destroy,
};

static const struct wlr_renderer_impl renderer_impl = {
	.get_texture_formats = vulkan_get_texture_formats,
	.get_render_formats = vulkan_get_render_formats,
	.destroy = vulkan_destroy,
	.get_drm_fd = vulkan_get_drm_fd,
	.texture_from_buffer = vulkan_texture_from_buffer,
	.begin_buffer_pass = vulkan_begin_buffer_pass,
	.render_timer_create = vulkan_render_timer_create,
};

// Initializes the VkDescriptorSetLayout and VkPipelineLayout needed
// for the texture rendering pipeline using the given VkSampler.
static bool init_tex_layouts(struct wlr_vk_renderer *renderer,
		VkSampler tex_sampler, VkDescriptorSetLayout *out_ds_layout,
		VkPipelineLayout *out_pipe_layout) {
	VkResult res;
	VkDevice dev = renderer->dev->dev;

	VkDescriptorSetLayoutBinding ds_binding = {
		.binding = 0,
		.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
		.descriptorCount = 1,
		.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
		.pImmutableSamplers = &tex_sampler,
	};

	VkDescriptorSetLayoutCreateInfo ds_info = {
		.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
		.bindingCount = 1,
		.pBindings = &ds_binding,
	};

	res = vkCreateDescriptorSetLayout(dev, &ds_info, NULL, out_ds_layout);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateDescriptorSetLayout", res);
		return false;
	}

	static_assert(sizeof(struct wlr_vk_vert_pcr_data) +
		sizeof(struct wlr_vk_frag_texture_pcr_data) < 128,
		"Expected to need <= 128 bytes of push constants");

	VkPushConstantRange pc_ranges[] = {
		{
			.size = sizeof(struct wlr_vk_vert_pcr_data),
			.stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
		},
		{
			.offset = pc_ranges[0].size,
			.size = sizeof(struct wlr_vk_frag_texture_pcr_data),
			.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
		},
	};

	VkPipelineLayoutCreateInfo pl_info = {
		.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
		.setLayoutCount = 1,
		.pSetLayouts = out_ds_layout,
		.pushConstantRangeCount = sizeof(pc_ranges) / sizeof(pc_ranges[0]),
		.pPushConstantRanges = pc_ranges,
	};

	res = vkCreatePipelineLayout(dev, &pl_info, NULL, out_pipe_layout);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreatePipelineLayout", res);
		return false;
	}

	return true;
}

static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) {
	VkResult res;
	VkDevice dev = renderer->dev->dev;

	VkDescriptorSetLayoutBinding ds_binding_input = {
		.binding = 0,
		.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
		.descriptorCount = 1,
		.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
		.pImmutableSamplers = NULL,
	};

	VkDescriptorSetLayoutCreateInfo ds_info = {
		.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
		.bindingCount = 1,
		.pBindings = &ds_binding_input,
	};

	res = vkCreateDescriptorSetLayout(dev, &ds_info, NULL, &renderer->output_ds_srgb_layout);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateDescriptorSetLayout", res);
		return false;
	}

	VkSamplerCreateInfo sampler_create_info = {
		.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
		.magFilter = VK_FILTER_LINEAR,
		.minFilter = VK_FILTER_LINEAR,
		.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
		.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
		.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
		.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
		.minLod = 0.f,
		.maxLod = 0.25f,
	};

	res = vkCreateSampler(renderer->dev->dev, &sampler_create_info, NULL,
		&renderer->output_sampler_lut3d);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateSampler", res);
		return false;
	}

	VkDescriptorSetLayoutBinding ds_binding_lut3d = {
		.binding = 0,
		.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
		.descriptorCount = 1,
		.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
		.pImmutableSamplers = &renderer->output_sampler_lut3d,
	};

	VkDescriptorSetLayoutCreateInfo ds_lut3d_info = {
		.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
		.bindingCount = 1,
		.pBindings = &ds_binding_lut3d,
	};

	res = vkCreateDescriptorSetLayout(dev, &ds_lut3d_info, NULL,
		&renderer->output_ds_lut3d_layout);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateDescriptorSetLayout", res);
		return false;
	}

	static_assert(sizeof(struct wlr_vk_vert_pcr_data) +
		sizeof(struct wlr_vk_frag_output_pcr_data) < 128,
		"Expected to need <= 128 bytes of push constants");

	// pipeline layout -- standard vertex uniforms, no shader uniforms
	VkPushConstantRange pc_ranges[] = {
		{
			.offset = 0,
			.size = sizeof(struct wlr_vk_vert_pcr_data),
			.stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
		},
		{
			.offset = sizeof(struct wlr_vk_vert_pcr_data),
			.size = sizeof(struct wlr_vk_frag_output_pcr_data),
			.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
		},
	};

	VkDescriptorSetLayout out_ds_layouts[] = {
		renderer->output_ds_srgb_layout,
		renderer->output_ds_lut3d_layout,
	};

	VkPipelineLayoutCreateInfo pl_info = {
		.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
		.setLayoutCount = sizeof(out_ds_layouts) / sizeof(out_ds_layouts[0]),
		.pSetLayouts = out_ds_layouts,
		.pushConstantRangeCount = sizeof(pc_ranges) / sizeof(pc_ranges[0]),
		.pPushConstantRanges = pc_ranges,
	};

	res = vkCreatePipelineLayout(dev, &pl_info, NULL, &renderer->output_pipe_layout);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreatePipelineLayout", res);
		return false;
	}

	return true;
}

static bool pipeline_layout_key_equals(
		const struct wlr_vk_pipeline_layout_key *a,
		const struct wlr_vk_pipeline_layout_key *b) {
	assert(!a->ycbcr.format || vulkan_format_is_ycbcr(a->ycbcr.format));
	assert(!b->ycbcr.format || vulkan_format_is_ycbcr(b->ycbcr.format));

	if (a->filter_mode != b->filter_mode) {
		return false;
	}

	if (a->ycbcr.format != b->ycbcr.format ||
			a->ycbcr.encoding != b->ycbcr.encoding ||
			a->ycbcr.range != b->ycbcr.range) {
		return false;
	}

	return true;
}

static bool pipeline_key_equals(const struct wlr_vk_pipeline_key *a,
		const struct wlr_vk_pipeline_key *b) {
	if (!pipeline_layout_key_equals(&a->layout, &b->layout)) {
		return false;
	}

	if (a->blend_mode != b->blend_mode) {
		return false;
	}

	if (a->source != b->source) {
		return false;
	}

	if (a->source == WLR_VK_SHADER_SOURCE_TEXTURE &&
			a->texture_transform != b->texture_transform) {
		return false;
	}

	return true;
}

static const VkVertexInputBindingDescription instance_vert_binding = {
	.binding = 0,
	.stride = sizeof(float) * 4,
	.inputRate = VK_VERTEX_INPUT_RATE_INSTANCE,
};
static const VkVertexInputAttributeDescription instance_vert_attr = {
	.location = 0,
	.binding = 0,
	.format = VK_FORMAT_R32G32B32A32_SFLOAT,
	.offset = 0,
};
static const VkPipelineVertexInputStateCreateInfo instance_vert_input = {
	.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
	.vertexBindingDescriptionCount = 1,
	.pVertexBindingDescriptions = &instance_vert_binding,
	.vertexAttributeDescriptionCount = 1,
	.pVertexAttributeDescriptions = &instance_vert_attr,
};

// Initializes the pipeline for rendering textures and using the given
// VkRenderPass and VkPipelineLayout.
struct wlr_vk_pipeline *setup_get_or_create_pipeline(
		struct wlr_vk_render_format_setup *setup,
		const struct wlr_vk_pipeline_key *key) {
	struct wlr_vk_pipeline *pipeline;
	wl_list_for_each(pipeline, &setup->pipelines, link) {
		if (pipeline_key_equals(&pipeline->key, key)) {
			return pipeline;
		}
	}

	struct wlr_vk_renderer *renderer = setup->renderer;

	struct wlr_vk_pipeline_layout *pipeline_layout = get_or_create_pipeline_layout(
		renderer, &key->layout);
	if (!pipeline_layout) {
		return NULL;
	}

	pipeline = calloc(1, sizeof(*pipeline));
	if (!pipeline) {
		return NULL;
	}

	pipeline->setup = setup;
	pipeline->key = *key;
	pipeline->layout = pipeline_layout;

	VkResult res;
	VkDevice dev = renderer->dev->dev;

	uint32_t color_transform_type = key->texture_transform;

	VkSpecializationMapEntry spec_entry = {
		.constantID = 0,
		.offset = 0,
		.size = sizeof(uint32_t),
	};

	VkSpecializationInfo specialization = {
		.mapEntryCount = 1,
		.pMapEntries = &spec_entry,
		.dataSize = sizeof(uint32_t),
		.pData = &color_transform_type,
	};

	VkPipelineShaderStageCreateInfo stages[2];
	stages[0] = (VkPipelineShaderStageCreateInfo) {
		.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
		.stage = VK_SHADER_STAGE_VERTEX_BIT,
		.module = renderer->vert_module,
		.pName = "main",
	};

	switch (key->source) {
	case WLR_VK_SHADER_SOURCE_SINGLE_COLOR:
		stages[1] = (VkPipelineShaderStageCreateInfo) {
			.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
			.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
			.module = renderer->quad_frag_module,
			.pName = "main",
		};
		break;
	case WLR_VK_SHADER_SOURCE_TEXTURE:
		stages[1] = (VkPipelineShaderStageCreateInfo) {
			.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
			.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
			.module = renderer->tex_frag_module,
			.pName = "main",
			.pSpecializationInfo = &specialization,
		};
		break;
	}

	VkPipelineInputAssemblyStateCreateInfo assembly = {
		.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
		.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN,
	};

	VkPipelineRasterizationStateCreateInfo rasterization = {
		.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
		.polygonMode = VK_POLYGON_MODE_FILL,
		.cullMode = VK_CULL_MODE_NONE,
		.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
		.lineWidth = 1.f,
	};

	VkPipelineColorBlendAttachmentState blend_attachment = {
		.blendEnable = key->blend_mode == WLR_RENDER_BLEND_MODE_PREMULTIPLIED,
		// we generally work with pre-multiplied alpha
		.srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
		.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
		.colorBlendOp = VK_BLEND_OP_ADD,
		.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
		.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
		.alphaBlendOp = VK_BLEND_OP_ADD,
		.colorWriteMask =
			VK_COLOR_COMPONENT_R_BIT |
			VK_COLOR_COMPONENT_G_BIT |
			VK_COLOR_COMPONENT_B_BIT |
			VK_COLOR_COMPONENT_A_BIT,
	};

	VkPipelineColorBlendStateCreateInfo blend = {
		.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
		.attachmentCount = 1,
		.pAttachments = &blend_attachment,
	};

	VkPipelineMultisampleStateCreateInfo multisample = {
		.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
		.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
	};

	VkPipelineViewportStateCreateInfo viewport = {
		.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
		.viewportCount = 1,
		.scissorCount = 1,
	};

	VkDynamicState dyn_states[] = {
		VK_DYNAMIC_STATE_VIEWPORT,
		VK_DYNAMIC_STATE_SCISSOR,
	};
	VkPipelineDynamicStateCreateInfo dynamic = {
		.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
		.pDynamicStates = dyn_states,
		.dynamicStateCount = sizeof(dyn_states) / sizeof(dyn_states[0]),
	};

	VkGraphicsPipelineCreateInfo pinfo = {
		.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
		.layout = pipeline_layout->vk,
		.renderPass = setup->render_pass,
		.subpass = 0,
		.stageCount = sizeof(stages) / sizeof(stages[0]),
		.pStages = stages,

		.pInputAssemblyState = &assembly,
		.pRasterizationState = &rasterization,
		.pColorBlendState = &blend,
		.pMultisampleState = &multisample,
		.pViewportState = &viewport,
		.pDynamicState = &dynamic,
		.pVertexInputState = &instance_vert_input,
	};

	VkPipelineCache cache = VK_NULL_HANDLE;
	res = vkCreateGraphicsPipelines(dev, cache, 1, &pinfo, NULL, &pipeline->vk);
	if (res != VK_SUCCESS) {
		wlr_vk_error("failed to create vulkan pipelines:", res);
		free(pipeline);
		return NULL;
	}

	wl_list_insert(&setup->pipelines, &pipeline->link);
	return pipeline;
}

static bool init_blend_to_output_pipeline(struct wlr_vk_renderer *renderer,
		VkRenderPass rp, VkPipelineLayout pipe_layout, VkPipeline *pipe,
		enum wlr_vk_output_transform transform) {
	VkResult res;
	VkDevice dev = renderer->dev->dev;

	uint32_t output_transform_type = transform;
	VkSpecializationMapEntry spec_entry = {
		.constantID = 0,
		.offset = 0,
		.size = sizeof(uint32_t),
	};
	VkSpecializationInfo specialization = {
		.mapEntryCount = 1,
		.pMapEntries = &spec_entry,
		.dataSize = sizeof(uint32_t),
		.pData = &output_transform_type,
	};

	VkPipelineShaderStageCreateInfo tex_stages[] = {
		{
			.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
			.stage = VK_SHADER_STAGE_VERTEX_BIT,
			.module = renderer->vert_module,
			.pName = "main",
		},
		{
			.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
			.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
			.module = renderer->output_module,
			.pName = "main",
			.pSpecializationInfo = &specialization,
		},
	};

	VkPipelineInputAssemblyStateCreateInfo assembly = {
		.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
		.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN,
	};

	VkPipelineRasterizationStateCreateInfo rasterization = {
		.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
		.polygonMode = VK_POLYGON_MODE_FILL,
		.cullMode = VK_CULL_MODE_NONE,
		.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
		.lineWidth = 1.f,
	};

	VkPipelineColorBlendAttachmentState blend_attachment = {
		.blendEnable = false,
		.colorWriteMask =
			VK_COLOR_COMPONENT_R_BIT |
			VK_COLOR_COMPONENT_G_BIT |
			VK_COLOR_COMPONENT_B_BIT |
			VK_COLOR_COMPONENT_A_BIT,
	};

	VkPipelineColorBlendStateCreateInfo blend = {
		.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
		.attachmentCount = 1,
		.pAttachments = &blend_attachment,
	};

	VkPipelineMultisampleStateCreateInfo multisample = {
		.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
		.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
	};

	VkPipelineViewportStateCreateInfo viewport = {
		.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
		.viewportCount = 1,
		.scissorCount = 1,
	};

	VkDynamicState dyn_states[] = {
		VK_DYNAMIC_STATE_VIEWPORT,
		VK_DYNAMIC_STATE_SCISSOR,
	};
	VkPipelineDynamicStateCreateInfo dynamic = {
		.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
		.pDynamicStates = dyn_states,
		.dynamicStateCount = sizeof(dyn_states) / sizeof(dyn_states[0]),
	};

	VkGraphicsPipelineCreateInfo pinfo = {
		.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
		.pNext = NULL,
		.layout = pipe_layout,
		.renderPass = rp,
		.subpass = 1, // second subpass!
		.stageCount = sizeof(tex_stages) / sizeof(tex_stages[0]),
		.pStages = tex_stages,
		.pInputAssemblyState = &assembly,
		.pRasterizationState = &rasterization,
		.pColorBlendState = &blend,
		.pMultisampleState = &multisample,
		.pViewportState = &viewport,
		.pDynamicState = &dynamic,
		.pVertexInputState = &instance_vert_input,
	};

	VkPipelineCache cache = VK_NULL_HANDLE;
	res = vkCreateGraphicsPipelines(dev, cache, 1, &pinfo, NULL, pipe);
	if (res != VK_SUCCESS) {
		wlr_vk_error("failed to create vulkan pipelines:", res);
		return false;
	}

	return true;
}

static VkSamplerYcbcrModelConversion ycbcr_model_from_wlr(enum wlr_color_encoding encoding) {
	switch (encoding) {
	case WLR_COLOR_ENCODING_NONE:
		abort(); // must be explicit
	case WLR_COLOR_ENCODING_BT601:
		return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601;
	case WLR_COLOR_ENCODING_BT709:
		return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709;
	case WLR_COLOR_ENCODING_BT2020:
		return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020;
	default:
		abort(); // unsupported
	}
}

static VkSamplerYcbcrRange ycbcr_range_from_wlr(enum wlr_color_range range) {
	switch (range) {
	case WLR_COLOR_RANGE_NONE:
		abort(); // must be explicit
	case WLR_COLOR_RANGE_LIMITED:
		return VK_SAMPLER_YCBCR_RANGE_ITU_NARROW;
	case WLR_COLOR_RANGE_FULL:
		return VK_SAMPLER_YCBCR_RANGE_ITU_FULL;
	}
	abort(); // unreachable
}

struct wlr_vk_pipeline_layout *get_or_create_pipeline_layout(
		struct wlr_vk_renderer *renderer,
		const struct wlr_vk_pipeline_layout_key *key) {
	struct wlr_vk_pipeline_layout *pipeline_layout;
	wl_list_for_each(pipeline_layout, &renderer->pipeline_layouts, link) {
		if (pipeline_layout_key_equals(&pipeline_layout->key, key)) {
			return pipeline_layout;
		}
	}

	pipeline_layout = calloc(1, sizeof(*pipeline_layout));
	if (!pipeline_layout) {
		return NULL;
	}

	pipeline_layout->key = *key;

	VkResult res;
	VkFilter filter = VK_FILTER_LINEAR;
	switch (key->filter_mode) {
	case WLR_SCALE_FILTER_BILINEAR:
		filter = VK_FILTER_LINEAR;
		break;
	case WLR_SCALE_FILTER_NEAREST:
		filter = VK_FILTER_NEAREST;
		break;
	}

	VkSamplerYcbcrConversionInfo conversion_info;
	VkSamplerCreateInfo sampler_create_info = {
		.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
		.magFilter = filter,
		.minFilter = filter,
		.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
		.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
		.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
		.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
		.minLod = 0.f,
		.maxLod = 0.25f,
	};

	if (key->ycbcr.format) {
		VkSamplerYcbcrConversionCreateInfo conversion_create_info = {
			.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
			.format = key->ycbcr.format->vk,
			.ycbcrModel = ycbcr_model_from_wlr(key->ycbcr.encoding),
			.ycbcrRange = ycbcr_range_from_wlr(key->ycbcr.range),
			.xChromaOffset = VK_CHROMA_LOCATION_MIDPOINT,
			.yChromaOffset = VK_CHROMA_LOCATION_MIDPOINT,
			.chromaFilter = VK_FILTER_LINEAR,
		};
		res = renderer->dev->api.vkCreateSamplerYcbcrConversionKHR(renderer->dev->dev,
			&conversion_create_info, NULL, &pipeline_layout->ycbcr.conversion);
		if (res != VK_SUCCESS) {
			wlr_vk_error("vkCreateSamplerYcbcrConversionKHR", res);
			free(pipeline_layout);
			return NULL;
		}

		conversion_info = (VkSamplerYcbcrConversionInfo){
			.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
			.conversion = pipeline_layout->ycbcr.conversion,
		};
		sampler_create_info.pNext = &conversion_info;
	} else {
		assert(key->ycbcr.encoding == WLR_COLOR_ENCODING_NONE || key->ycbcr.encoding == WLR_COLOR_ENCODING_IDENTITY);
		assert(key->ycbcr.range == WLR_COLOR_RANGE_NONE || key->ycbcr.range == WLR_COLOR_RANGE_FULL);
	}

	res = vkCreateSampler(renderer->dev->dev, &sampler_create_info, NULL, &pipeline_layout->sampler);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateSampler", res);
		free(pipeline_layout);
		return NULL;
	}

	if (!init_tex_layouts(renderer, pipeline_layout->sampler, &pipeline_layout->ds, &pipeline_layout->vk)) {
		free(pipeline_layout);
		return NULL;
	}

	wl_list_insert(&renderer->pipeline_layouts, &pipeline_layout->link);
	return pipeline_layout;
}


/* The fragment shader for the blend->image subpass can be configured to either
 * use or not a sampler3d lookup table; however, even if the shader does not use
 * the sampler, a valid descriptor set should be bound. Create that here, linked to
 * a 1x1x1 image.
 */
static bool init_dummy_images(struct wlr_vk_renderer *renderer) {
	VkResult res;
	VkDevice dev = renderer->dev->dev;

	VkFormat format = VK_FORMAT_R32G32B32A32_SFLOAT;

	VkImageCreateInfo img_info = {
		.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
		.imageType = VK_IMAGE_TYPE_3D,
		.format = format,
		.mipLevels = 1,
		.arrayLayers = 1,
		.samples = VK_SAMPLE_COUNT_1_BIT,
		.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
		.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
		.extent = (VkExtent3D) { 1, 1, 1 },
		.tiling = VK_IMAGE_TILING_OPTIMAL,
		.usage = VK_IMAGE_USAGE_SAMPLED_BIT,
	};
	res = vkCreateImage(dev, &img_info, NULL, &renderer->dummy3d_image);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateImage failed", res);
		return false;
	}

	VkMemoryRequirements mem_reqs = {0};
	vkGetImageMemoryRequirements(dev, renderer->dummy3d_image, &mem_reqs);
	int mem_type_index = vulkan_find_mem_type(renderer->dev,
		VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mem_reqs.memoryTypeBits);
	if (mem_type_index == -1) {
		wlr_log(WLR_ERROR, "Failed to find suitable memory type");
		return false;
	}
	VkMemoryAllocateInfo mem_info = {
		.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
		.allocationSize = mem_reqs.size,
		.memoryTypeIndex = mem_type_index,
	};
	res = vkAllocateMemory(dev, &mem_info, NULL, &renderer->dummy3d_mem);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkAllocateMemory failed", res);
		return false;
	}
	res = vkBindImageMemory(dev, renderer->dummy3d_image, renderer->dummy3d_mem, 0);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkBindMemory failed", res);
		return false;
	}

	VkImageViewCreateInfo view_info = {
		.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
		.viewType = VK_IMAGE_VIEW_TYPE_3D,
		.format = format,
		.components.r = VK_COMPONENT_SWIZZLE_IDENTITY,
		.components.g = VK_COMPONENT_SWIZZLE_IDENTITY,
		.components.b = VK_COMPONENT_SWIZZLE_IDENTITY,
		.components.a = VK_COMPONENT_SWIZZLE_IDENTITY,
		.subresourceRange = (VkImageSubresourceRange) {
			.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
			.baseMipLevel = 0,
			.levelCount = 1,
			.baseArrayLayer = 0,
			.layerCount = 1,
		},
		.image = renderer->dummy3d_image,
	};
	res = vkCreateImageView(dev, &view_info, NULL, &renderer->dummy3d_image_view);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateImageView failed", res);
		return false;
	}

	renderer->output_ds_lut3d_dummy_pool = vulkan_alloc_texture_ds(renderer,
		renderer->output_ds_lut3d_layout, &renderer->output_ds_lut3d_dummy);
	if (!renderer->output_ds_lut3d_dummy_pool) {
		wlr_log(WLR_ERROR, "Failed to allocate descriptor");
		return false;
	}
	VkDescriptorImageInfo ds_img_info = {
		.imageView = renderer->dummy3d_image_view,
		.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
	};
	VkWriteDescriptorSet ds_write = {
		.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
		.descriptorCount = 1,
		.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
		.dstSet = renderer->output_ds_lut3d_dummy,
		.pImageInfo = &ds_img_info,
	};
	vkUpdateDescriptorSets(dev, 1, &ds_write, 0, NULL);

	return true;
}

// Creates static render data, such as sampler, layouts and shader modules
// for the given renderer.
// Cleanup is done by destroying the renderer.
static bool init_static_render_data(struct wlr_vk_renderer *renderer) {
	VkResult res;
	VkDevice dev = renderer->dev->dev;

	if (!init_blend_to_output_layouts(renderer)) {
		return false;
	}

	if (!init_dummy_images(renderer)) {
		return false;
	}

	// load vert module and tex frag module since they are needed to
	// initialize the tex pipeline
	VkShaderModuleCreateInfo sinfo = {
		.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
		.codeSize = sizeof(common_vert_data),
		.pCode = common_vert_data,
	};
	res = vkCreateShaderModule(dev, &sinfo, NULL, &renderer->vert_module);
	if (res != VK_SUCCESS) {
		wlr_vk_error("Failed to create vertex shader module", res);
		return false;
	}

	sinfo = (VkShaderModuleCreateInfo){
		.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
		.codeSize = sizeof(texture_frag_data),
		.pCode = texture_frag_data,
	};
	res = vkCreateShaderModule(dev, &sinfo, NULL, &renderer->tex_frag_module);
	if (res != VK_SUCCESS) {
		wlr_vk_error("Failed to create tex fragment shader module", res);
		return false;
	}

	sinfo = (VkShaderModuleCreateInfo){
		.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
		.codeSize = sizeof(quad_frag_data),
		.pCode = quad_frag_data,
	};
	res = vkCreateShaderModule(dev, &sinfo, NULL, &renderer->quad_frag_module);
	if (res != VK_SUCCESS) {
		wlr_vk_error("Failed to create quad fragment shader module", res);
		return false;
	}

	sinfo = (VkShaderModuleCreateInfo){
		.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
		.codeSize = sizeof(output_frag_data),
		.pCode = output_frag_data,
	};
	res = vkCreateShaderModule(dev, &sinfo, NULL, &renderer->output_module);
	if (res != VK_SUCCESS) {
		wlr_vk_error("Failed to create blend->output fragment shader module", res);
		return false;
	}

	return true;
}

static struct wlr_vk_render_format_setup *find_or_create_render_setup(
		struct wlr_vk_renderer *renderer, const struct wlr_vk_format *format,
		bool use_blending_buffer, bool srgb) {
	struct wlr_vk_render_format_setup *setup;
	wl_list_for_each(setup, &renderer->render_format_setups, link) {
		if (setup->render_format == format &&
				setup->use_blending_buffer == use_blending_buffer &&
				setup->use_srgb == srgb) {
			return setup;
		}
	}

	setup = calloc(1u, sizeof(*setup));
	if (!setup) {
		wlr_log(WLR_ERROR, "Allocation failed");
		return NULL;
	}

	setup->render_format = format;
	setup->use_blending_buffer = use_blending_buffer;
	setup->use_srgb = srgb;
	setup->renderer = renderer;
	wl_list_init(&setup->pipelines);

	VkDevice dev = renderer->dev->dev;
	VkResult res;

	if (use_blending_buffer) {
		VkAttachmentDescription attachments[] = {
			{
				.format = VK_FORMAT_R16G16B16A16_SFLOAT,
				.samples = VK_SAMPLE_COUNT_1_BIT,
				.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
				.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
				.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
				.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
				.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
				.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
			},
			{
				.format = format->vk,
				.samples = VK_SAMPLE_COUNT_1_BIT,
				.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
				.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
				.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
				.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
				.initialLayout = VK_IMAGE_LAYOUT_GENERAL,
				.finalLayout = VK_IMAGE_LAYOUT_GENERAL,
			}
		};

		VkAttachmentReference blend_write_ref = {
			.attachment = 0u,
			.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
		};

		VkAttachmentReference blend_read_ref = {
			.attachment = 0u,
			.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
		};

		VkAttachmentReference color_ref = {
			.attachment = 1u,
			.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
		};

		VkSubpassDescription subpasses[] = {
			{
				.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
				.colorAttachmentCount = 1,
				.pColorAttachments = &blend_write_ref,
			},
			{
				.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
				.inputAttachmentCount = 1,
				.pInputAttachments = &blend_read_ref,
				.colorAttachmentCount = 1,
				.pColorAttachments = &color_ref,
			}
		};

		VkSubpassDependency deps[] = {
			{
				.srcSubpass = VK_SUBPASS_EXTERNAL,
				.srcStageMask = VK_PIPELINE_STAGE_HOST_BIT |
					VK_PIPELINE_STAGE_TRANSFER_BIT |
					VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT |
					VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
				.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT |
					VK_ACCESS_TRANSFER_WRITE_BIT |
					VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
				.dstSubpass = 0,
				.dstStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
				.dstAccessMask = VK_ACCESS_UNIFORM_READ_BIT |
					VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT |
					VK_ACCESS_INDIRECT_COMMAND_READ_BIT |
					VK_ACCESS_SHADER_READ_BIT,
			},
			{
				.srcSubpass = 0,
				.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
				.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
				.dstSubpass = 1,
				.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
				.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
				.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT,
			},
			{
				.srcSubpass = 1,
				.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
				.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
				.dstSubpass = VK_SUBPASS_EXTERNAL,
				.dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT |
					VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
				.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT |
					VK_ACCESS_MEMORY_READ_BIT,
			},
		};

		VkRenderPassCreateInfo rp_info = {
			.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
			.pNext = NULL,
			.flags = 0,
			.attachmentCount = sizeof(attachments) / sizeof(attachments[0]),
			.pAttachments = attachments,
			.subpassCount = sizeof(subpasses) / sizeof(subpasses[0]),
			.pSubpasses = subpasses,
			.dependencyCount = sizeof(deps) / sizeof(deps[0]),
			.pDependencies = deps,
		};

		res = vkCreateRenderPass(dev, &rp_info, NULL, &setup->render_pass);
		if (res != VK_SUCCESS) {
			wlr_vk_error("Failed to create 2-step render pass", res);
			goto error;
		}

		attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
		attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
		res = vkCreateRenderPass(dev, &rp_info, NULL, &setup->render_pass_clear);
		if (res != VK_SUCCESS) {
			wlr_vk_error("Failed to create 2-step clear render pass", res);
			goto error;
		}

		// this is only well defined if render pass has a 2nd subpass
		if (!init_blend_to_output_pipeline(
				renderer, setup->render_pass, renderer->output_pipe_layout,
				&setup->output_pipe_identity, WLR_VK_OUTPUT_TRANSFORM_IDENTITY)) {
			goto error;
		}
		if (!init_blend_to_output_pipeline(
				renderer, setup->render_pass, renderer->output_pipe_layout,
				&setup->output_pipe_lut3d, WLR_VK_OUTPUT_TRANSFORM_LUT3D)) {
			goto error;
		}
		if (!init_blend_to_output_pipeline(
				renderer, setup->render_pass, renderer->output_pipe_layout,
				&setup->output_pipe_srgb, WLR_VK_OUTPUT_TRANSFORM_INVERSE_SRGB)) {
			goto error;
		}
		if (!init_blend_to_output_pipeline(
				renderer, setup->render_pass, renderer->output_pipe_layout,
				&setup->output_pipe_pq, WLR_VK_OUTPUT_TRANSFORM_INVERSE_ST2084_PQ)) {
			goto error;
		}
		if (!init_blend_to_output_pipeline(
				renderer, setup->render_pass, renderer->output_pipe_layout,
				&setup->output_pipe_gamma22, WLR_VK_OUTPUT_TRANSFORM_INVERSE_GAMMA22)) {
			goto error;
		}
		if (!init_blend_to_output_pipeline(
			renderer, setup->render_pass, renderer->output_pipe_layout,
			&setup->output_pipe_bt1886, WLR_VK_OUTPUT_TRANSFORM_INVERSE_BT1886)) {
			goto error;
		}
	} else {
		VkAttachmentDescription attachment = {
			.format = format->vk,
			.samples = VK_SAMPLE_COUNT_1_BIT,
			.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
			.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
			.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
			.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
			.initialLayout = VK_IMAGE_LAYOUT_GENERAL,
			.finalLayout = VK_IMAGE_LAYOUT_GENERAL,
		};
		if (srgb) {
			assert(format->vk_srgb);
			attachment.format = format->vk_srgb;
		}

		VkAttachmentReference color_ref = {
			.attachment = 0u,
			.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
		};

		VkSubpassDescription subpass = {
			.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
			.colorAttachmentCount = 1,
			.pColorAttachments = &color_ref,
		};

		VkSubpassDependency deps[] = {
			{
				.srcSubpass = VK_SUBPASS_EXTERNAL,
				.srcStageMask = VK_PIPELINE_STAGE_HOST_BIT |
					VK_PIPELINE_STAGE_TRANSFER_BIT |
					VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT |
					VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
				.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT |
					VK_ACCESS_TRANSFER_WRITE_BIT |
					VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
				.dstSubpass = 0,
				.dstStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
				.dstAccessMask = VK_ACCESS_UNIFORM_READ_BIT |
					VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT |
					VK_ACCESS_INDIRECT_COMMAND_READ_BIT |
					VK_ACCESS_SHADER_READ_BIT,
			},
			{
				.srcSubpass = 0,
				.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
				.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
				.dstSubpass = VK_SUBPASS_EXTERNAL,
				.dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT |
					VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
				.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT |
					VK_ACCESS_MEMORY_READ_BIT,
			},
		};

		VkRenderPassCreateInfo rp_info = {
			.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
			.attachmentCount = 1,
			.pAttachments = &attachment,
			.subpassCount = 1,
			.pSubpasses = &subpass,
			.dependencyCount = sizeof(deps) / sizeof(deps[0]),
			.pDependencies = deps,
		};

		res = vkCreateRenderPass(dev, &rp_info, NULL, &setup->render_pass);
		if (res != VK_SUCCESS) {
			wlr_vk_error("Failed to create render pass", res);
			goto error;
		}
	}

	if (!setup_get_or_create_pipeline(setup, &(struct wlr_vk_pipeline_key){
		.source = WLR_VK_SHADER_SOURCE_SINGLE_COLOR,
		.layout = {0},
	})) {
		goto error;
	}

	if (!setup_get_or_create_pipeline(setup, &(struct wlr_vk_pipeline_key){
		.source = WLR_VK_SHADER_SOURCE_TEXTURE,
		.texture_transform = WLR_VK_TEXTURE_TRANSFORM_IDENTITY,
		.layout = {0},
	})) {
		goto error;
	}

	if (!setup_get_or_create_pipeline(setup, &(struct wlr_vk_pipeline_key){
		.source = WLR_VK_SHADER_SOURCE_TEXTURE,
		.texture_transform = WLR_VK_TEXTURE_TRANSFORM_SRGB,
		.layout = {0},
	})) {
		goto error;
	}

	wl_list_insert(&renderer->render_format_setups, &setup->link);
	return setup;

error:
	destroy_render_format_setup(renderer, setup);
	return NULL;
}

struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev) {
	struct wlr_vk_renderer *renderer;
	VkResult res;
	if (!(renderer = calloc(1, sizeof(*renderer)))) {
		wlr_log_errno(WLR_ERROR, "failed to allocate wlr_vk_renderer");
		return NULL;
	}

	renderer->dev = dev;
	wlr_renderer_init(&renderer->wlr_renderer, &renderer_impl, WLR_BUFFER_CAP_DMABUF);
	renderer->wlr_renderer.features.input_color_transform = true;
	renderer->wlr_renderer.features.output_color_transform = true;
	wl_list_init(&renderer->stage.buffers);
	wl_list_init(&renderer->foreign_textures);
	wl_list_init(&renderer->textures);
	wl_list_init(&renderer->descriptor_pools);
	wl_list_init(&renderer->output_descriptor_pools);
	wl_list_init(&renderer->render_format_setups);
	wl_list_init(&renderer->render_buffers);
	wl_list_init(&renderer->color_transforms);
	wl_list_init(&renderer->pipeline_layouts);

	renderer->wlr_renderer.color_encodings =
		WLR_COLOR_ENCODING_BT601 |
		WLR_COLOR_ENCODING_BT709 |
		WLR_COLOR_ENCODING_BT2020;

	uint64_t cap_syncobj_timeline;
	if (dev->drm_fd >= 0 && drmGetCap(dev->drm_fd, DRM_CAP_SYNCOBJ_TIMELINE, &cap_syncobj_timeline) == 0) {
		renderer->wlr_renderer.features.timeline = dev->sync_file_import_export && cap_syncobj_timeline != 0;
	}

	if (!init_static_render_data(renderer)) {
		goto error;
	}

	VkCommandPoolCreateInfo cpool_info = {
		.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
		.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
		.queueFamilyIndex = dev->queue_family,
	};
	res = vkCreateCommandPool(dev->dev, &cpool_info, NULL,
		&renderer->command_pool);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateCommandPool", res);
		goto error;
	}

	VkSemaphoreTypeCreateInfoKHR semaphore_type_info = {
		.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO_KHR,
		.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE_KHR,
		.initialValue = 0,
	};
	VkSemaphoreCreateInfo semaphore_info = {
		.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
		.pNext = &semaphore_type_info,
	};
	res = vkCreateSemaphore(dev->dev, &semaphore_info, NULL,
		&renderer->timeline_semaphore);
	if (res != VK_SUCCESS) {
		wlr_vk_error("vkCreateSemaphore", res);
		goto error;
	}

	return &renderer->wlr_renderer;

error:
	vulkan_destroy(&renderer->wlr_renderer);
	return NULL;
}

struct wlr_renderer *wlr_vk_renderer_create_with_drm_fd(int drm_fd) {
	wlr_log(WLR_INFO, "The vulkan renderer is only experimental and "
		"not expected to be ready for daily use");
	wlr_log(WLR_INFO, "Run with VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation "
		"to enable the validation layer");

	struct wlr_vk_instance *ini = vulkan_instance_create(default_debug);
	if (!ini) {
		wlr_log(WLR_ERROR, "creating vulkan instance for renderer failed");
		return NULL;
	}

	VkPhysicalDevice phdev = vulkan_find_drm_phdev(ini, drm_fd);
	if (!phdev) {
		// We rather fail here than doing some guesswork
		wlr_log(WLR_ERROR, "Could not match drm and vulkan device");
		goto error;
	}

	struct wlr_vk_device *dev = vulkan_device_create(ini, phdev);
	if (!dev) {
		wlr_log(WLR_ERROR, "Failed to create vulkan device");
		goto error;
	}

	// Do not use the drm_fd that was passed in: we should prefer the render
	// node even if a primary node was provided
	dev->drm_fd = vulkan_open_phdev_drm_fd(phdev);

	return vulkan_renderer_create_for_device(dev);

error:
	vulkan_instance_destroy(ini);
	return NULL;
}

VkInstance wlr_vk_renderer_get_instance(struct wlr_renderer *renderer) {
	struct wlr_vk_renderer *vk_renderer = vulkan_get_renderer(renderer);
	return vk_renderer->dev->instance->instance;
}

VkPhysicalDevice wlr_vk_renderer_get_physical_device(struct wlr_renderer *renderer) {
	struct wlr_vk_renderer *vk_renderer = vulkan_get_renderer(renderer);
	return vk_renderer->dev->phdev;
}

VkDevice wlr_vk_renderer_get_device(struct wlr_renderer *renderer) {
	struct wlr_vk_renderer *vk_renderer = vulkan_get_renderer(renderer);
	return vk_renderer->dev->dev;
}

uint32_t wlr_vk_renderer_get_queue_family(struct wlr_renderer *renderer) {
	struct wlr_vk_renderer *vk_renderer = vulkan_get_renderer(renderer);
	return vk_renderer->dev->queue_family;
}
