aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/render/vulkan.h21
-rw-r--r--render/vulkan/pixel_format.c3
-rw-r--r--render/vulkan/renderer.c695
-rw-r--r--render/vulkan/shaders/meson.build1
-rw-r--r--render/vulkan/shaders/output.frag21
5 files changed, 651 insertions, 90 deletions
diff --git a/include/render/vulkan.h b/include/render/vulkan.h
index 5d00fec1..202b4200 100644
--- a/include/render/vulkan.h
+++ b/include/render/vulkan.h
@@ -140,6 +140,7 @@ struct wlr_vk_render_format_setup {
VkPipeline tex_srgb_pipe;
VkPipeline tex_nv12_pipe;
VkPipeline quad_pipe;
+ VkPipeline output_pipe;
};
// Renderer-internal represenation of an wlr_buffer imported for rendering.
@@ -156,6 +157,13 @@ struct wlr_vk_render_buffer {
uint32_t mem_count;
VkDeviceMemory memories[WLR_DMABUF_MAX_PLANES];
bool transitioned;
+
+ VkImage blend_image;
+ VkImageView blend_image_view;
+ VkDeviceMemory blend_memory;
+ VkDescriptorSet blend_descriptor_set;
+ struct wlr_vk_descriptor_pool *blend_attachment_pool;
+ bool blend_transitioned;
};
struct wlr_vk_command_buffer {
@@ -184,11 +192,17 @@ struct wlr_vk_renderer {
VkShaderModule vert_module;
VkShaderModule tex_frag_module;
VkShaderModule quad_frag_module;
+ VkShaderModule output_module;
VkDescriptorSetLayout ds_layout, nv12_ds_layout;
VkPipelineLayout pipe_layout, nv12_pipe_layout;
VkSampler sampler, nv12_sampler;
VkSamplerYcbcrConversion nv12_conversion;
+ // for blend->output subpass
+ VkPipelineLayout output_pipe_layout;
+ VkDescriptorSetLayout output_ds_layout;
+ size_t last_output_pool_size;
+ struct wl_list output_descriptor_pools; // wlr_vk_descriptor_pool.link
VkSemaphore timeline_semaphore;
uint64_t timeline_point;
@@ -208,6 +222,7 @@ struct wlr_vk_renderer {
struct wl_list descriptor_pools; // wlr_vk_descriptor_pool.link
struct wl_list render_format_setups; // wlr_vk_render_format_setup.link
+
struct wl_list textures; // wlr_vk_texture.link
// Textures to return to foreign queue
struct wl_list foreign_textures; // wlr_vk_texture.foreign_link
@@ -258,6 +273,12 @@ struct wlr_vk_descriptor_pool *vulkan_alloc_texture_ds(
struct wlr_vk_renderer *renderer, VkDescriptorSetLayout ds_layout,
VkDescriptorSet *ds);
+// Tries to allocate a descriptor set for the blending image. Will
+// additionally return the pool it was allocated from when successful
+// (for freeing it later).
+struct wlr_vk_descriptor_pool *vulkan_alloc_blend_ds(
+ struct wlr_vk_renderer *renderer, VkDescriptorSet *ds);
+
// Frees the given descriptor set from the pool its pool.
void vulkan_free_ds(struct wlr_vk_renderer *renderer,
struct wlr_vk_descriptor_pool *pool, VkDescriptorSet ds);
diff --git a/render/vulkan/pixel_format.c b/render/vulkan/pixel_format.c
index 59be2041..02442e39 100644
--- a/render/vulkan/pixel_format.c
+++ b/render/vulkan/pixel_format.c
@@ -293,10 +293,9 @@ static bool query_modifier_support(struct wlr_vk_device *dev,
char render_status[256], texture_status[256];
// check that specific modifier for render usage
- // also, only allow rendering to formats with SRGB encoding
const char *errmsg = "unknown error";
if ((m.drmFormatModifierTilingFeatures & render_features) == render_features &&
- props->format.is_srgb && !props->format.is_ycbcr) {
+ !props->format.is_ycbcr) {
struct wlr_vk_format_modifier_props p = {0};
if (query_modifier_usage_support(dev, props->format.vk, render_usage, &m, &p, &errmsg)) {
props->dmabuf.render_mods[props->dmabuf.render_mod_count++] = p;
diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c
index b704caca..cf8c6a1f 100644
--- a/render/vulkan/renderer.c
+++ b/render/vulkan/renderer.c
@@ -24,6 +24,7 @@
#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 "types/wlr_buffer.h"
#include "types/wlr_matrix.h"
@@ -54,7 +55,7 @@ struct wlr_vk_renderer *vulkan_get_renderer(struct wlr_renderer *wlr_renderer) {
}
static struct wlr_vk_render_format_setup *find_or_create_render_setup(
- struct wlr_vk_renderer *renderer, VkFormat format);
+ struct wlr_vk_renderer *renderer, VkFormat format, bool has_blending_buffer);
// vertex shader push constant range data
struct vert_pcr_data {
@@ -86,14 +87,15 @@ static void mat3_to_mat4(const float mat3[9], float mat4[4][4]) {
mat4[3][3] = 1.f;
}
-struct wlr_vk_descriptor_pool *vulkan_alloc_texture_ds(
- struct wlr_vk_renderer *renderer, VkDescriptorSetLayout ds_layout,
- VkDescriptorSet *ds) {
+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;
bool found = false;
struct wlr_vk_descriptor_pool *pool;
- wl_list_for_each(pool, &renderer->descriptor_pools, link) {
+ wl_list_for_each(pool, pool_list, link) {
if (pool->free > 0) {
found = true;
break;
@@ -107,7 +109,7 @@ struct wlr_vk_descriptor_pool *vulkan_alloc_texture_ds(
return NULL;
}
- size_t count = 2 * renderer->last_pool_size;
+ size_t count = 2 * (*last_pool_size);
if (!count) {
count = start_descriptor_pool_size;
}
@@ -115,7 +117,7 @@ struct wlr_vk_descriptor_pool *vulkan_alloc_texture_ds(
pool->free = count;
VkDescriptorPoolSize pool_size = {
.descriptorCount = count,
- .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .type = type,
};
VkDescriptorPoolCreateInfo dpool_info = {
@@ -134,14 +136,14 @@ struct wlr_vk_descriptor_pool *vulkan_alloc_texture_ds(
return NULL;
}
- renderer->last_pool_size = count;
- wl_list_insert(&renderer->descriptor_pools, &pool->link);
+ *last_pool_size = count;
+ wl_list_insert(pool_list, &pool->link);
}
VkDescriptorSetAllocateInfo ds_info = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
.descriptorSetCount = 1,
- .pSetLayouts = &ds_layout,
+ .pSetLayouts = layout,
.descriptorPool = pool->pool,
};
res = vkAllocateDescriptorSets(renderer->dev->dev, &ds_info, ds);
@@ -154,6 +156,21 @@ struct wlr_vk_descriptor_pool *vulkan_alloc_texture_ds(
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_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);
@@ -171,6 +188,7 @@ static void destroy_render_format_setup(struct wlr_vk_renderer *renderer,
vkDestroyPipeline(dev, setup->tex_identity_pipe, NULL);
vkDestroyPipeline(dev, setup->tex_srgb_pipe, NULL);
vkDestroyPipeline(dev, setup->tex_nv12_pipe, NULL);
+ vkDestroyPipeline(dev, setup->output_pipe, NULL);
vkDestroyPipeline(dev, setup->quad_pipe, NULL);
}
@@ -584,6 +602,14 @@ static void destroy_render_buffer(struct wlr_vk_render_buffer *buffer) {
vkFreeMemory(dev, buffer->memories[i], NULL);
}
+ vkDestroyImage(dev, buffer->blend_image, NULL);
+ vkFreeMemory(dev, buffer->blend_memory, NULL);
+ vkDestroyImageView(dev, buffer->blend_image_view, NULL);
+ if (buffer->blend_attachment_pool) {
+ vulkan_free_ds(buffer->renderer, buffer->blend_attachment_pool,
+ buffer->blend_descriptor_set);
+ }
+
free(buffer);
}
@@ -597,9 +623,119 @@ static struct wlr_addon_interface render_buffer_addon_impl = {
.destroy = handle_render_buffer_destroy,
};
+static bool setup_blend_image(struct wlr_vk_renderer *renderer,
+ struct wlr_vk_render_buffer *buffer, int32_t width, int32_t height) {
+ VkResult res;
+ VkDevice dev = renderer->dev->dev;
+
+ // 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) { width, height, 1 },
+ .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT,
+ };
+
+ res = vkCreateImage(dev, &img_info, NULL, &buffer->blend_image);
+ if (res != VK_SUCCESS) {
+ wlr_vk_error("vkCreateImage failed", res);
+ goto error;
+ }
+
+ VkMemoryRequirements mem_reqs;
+ vkGetImageMemoryRequirements(dev, buffer->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->blend_memory);
+ if (res != VK_SUCCESS) {
+ wlr_vk_error("vkAllocatorMemory failed", res);
+ goto error;
+ }
+
+ res = vkBindImageMemory(dev, buffer->blend_image, buffer->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->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->blend_image_view);
+ if (res != VK_SUCCESS) {
+ wlr_vk_error("vkCreateImageView failed", res);
+ goto error;
+ }
+
+ buffer->blend_attachment_pool = vulkan_alloc_blend_ds(renderer,
+ &buffer->blend_descriptor_set);
+ if (!buffer->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->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->blend_descriptor_set,
+ .dstBinding = 0,
+ .pImageInfo = &ds_attach_info,
+ };
+ vkUpdateDescriptorSets(dev, 1, &ds_write, 0, NULL);
+ return true;
+
+error:
+ // cleaning up blend_attachment_pool, blend_descriptor_set, blend_image,
+ // blend_memory, and blend_image_view 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) {
VkResult res;
+ VkDevice dev = renderer->dev->dev;
struct wlr_vk_render_buffer *buffer = calloc(1, sizeof(*buffer));
if (buffer == NULL) {
@@ -611,7 +747,7 @@ static struct wlr_vk_render_buffer *create_render_buffer(
struct wlr_dmabuf_attributes dmabuf = {0};
if (!wlr_buffer_get_dmabuf(wlr_buffer, &dmabuf)) {
- goto error_buffer;
+ goto error;
}
wlr_log(WLR_DEBUG, "vulkan create_render_buffer: %.4s, %dx%d",
@@ -620,16 +756,15 @@ static struct wlr_vk_render_buffer *create_render_buffer(
buffer->image = vulkan_import_dmabuf(renderer, &dmabuf,
buffer->memories, &buffer->mem_count, true);
if (!buffer->image) {
- goto error_buffer;
+ goto error;
}
- VkDevice dev = renderer->dev->dev;
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_buffer;
+ goto error;
}
VkImageViewCreateInfo view_info = {
@@ -653,18 +788,32 @@ static struct wlr_vk_render_buffer *create_render_buffer(
res = vkCreateImageView(dev, &view_info, NULL, &buffer->image_view);
if (res != VK_SUCCESS) {
wlr_vk_error("vkCreateImageView failed", res);
- goto error_view;
+ goto error;
}
- buffer->render_setup = find_or_create_render_setup(renderer, fmt->format.vk);
+ bool has_blending_buffer = !fmt->format.is_srgb;
+
+ buffer->render_setup = find_or_create_render_setup(
+ renderer, fmt->format.vk, has_blending_buffer);
if (!buffer->render_setup) {
- goto error_view;
+ goto error;
+ }
+
+ VkImageView attachments[2] = {0};
+ uint32_t attachment_count = 0;
+
+ if (has_blending_buffer) {
+ if (!setup_blend_image(renderer, buffer, dmabuf.width, dmabuf.height)) {
+ goto error;
+ }
+ attachments[attachment_count++] = buffer->blend_image_view;
}
+ attachments[attachment_count++] = buffer->image_view;
VkFramebufferCreateInfo fb_info = {
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
- .attachmentCount = 1u,
- .pAttachments = &buffer->image_view,
+ .attachmentCount = attachment_count,
+ .pAttachments = attachments,
.flags = 0u,
.width = dmabuf.width,
.height = dmabuf.height,
@@ -675,23 +824,32 @@ static struct wlr_vk_render_buffer *create_render_buffer(
res = vkCreateFramebuffer(dev, &fb_info, NULL, &buffer->framebuffer);
if (res != VK_SUCCESS) {
wlr_vk_error("vkCreateFramebuffer", res);
- goto error_view;
+ goto error;
}
+
wlr_addon_init(&buffer->addon, &wlr_buffer->addons, renderer,
&render_buffer_addon_impl);
wl_list_insert(&renderer->render_buffers, &buffer->link);
return buffer;
-error_view:
+error:
+ if (buffer->blend_attachment_pool) {
+ vulkan_free_ds(buffer->renderer, buffer->blend_attachment_pool,
+ buffer->blend_descriptor_set);
+ }
+ vkDestroyImage(dev, buffer->blend_image, NULL);
+ vkFreeMemory(dev, buffer->blend_memory, NULL);
+ vkDestroyImageView(dev, buffer->blend_image_view, NULL);
+
vkDestroyFramebuffer(dev, buffer->framebuffer, NULL);
vkDestroyImageView(dev, buffer->image_view, NULL);
vkDestroyImage(dev, buffer->image, NULL);
for (size_t i = 0u; i < buffer->mem_count; ++i) {
vkFreeMemory(dev, buffer->memories[i], NULL);
}
-error_buffer:
+
wlr_dmabuf_attributes_finish(&dmabuf);
free(buffer);
return NULL;
@@ -919,12 +1077,45 @@ static void vulkan_end(struct wlr_renderer *wlr_renderer) {
assert(stage_cb != NULL);
renderer->stage.cb = NULL;
+ struct wlr_vk_render_buffer *current_rb = renderer->current_render_buffer;
+
+ if (current_rb->blend_image) {
+ // Apply output shader to map blend image to actual output image
+ vkCmdNextSubpass(render_cb->vk, VK_SUBPASS_CONTENTS_INLINE);
+
+ VkPipeline pipe = current_rb->render_setup->output_pipe;
+ if (pipe != renderer->bound_pipe) {
+ vkCmdBindPipeline(render_cb->vk, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
+ renderer->bound_pipe = pipe;
+ }
+
+ float final_matrix[9] = {
+ renderer->render_width, 0.f, -1.f,
+ 0.f, renderer->render_height, -1.f,
+ 0.f, 0.f, 0.f,
+ };
+ struct vert_pcr_data vert_pcr_data;
+ mat3_to_mat4(final_matrix, vert_pcr_data.mat4);
+ vert_pcr_data.uv_off[0] = 0.f;
+ vert_pcr_data.uv_off[1] = 0.f;
+ vert_pcr_data.uv_size[0] = 1.f;
+ vert_pcr_data.uv_size[1] = 1.f;
+
+ vkCmdPushConstants(render_cb->vk, renderer->output_pipe_layout,
+ VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(vert_pcr_data), &vert_pcr_data);
+ vkCmdBindDescriptorSets(render_cb->vk,
+ VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->output_pipe_layout,
+ 0, 1, &current_rb->blend_descriptor_set, 0, NULL);
+
+ vkCmdDraw(render_cb->vk, 4, 1, 0, 0);
+ }
+
+ vkCmdEndRenderPass(render_cb->vk);
+
renderer->render_width = 0u;
renderer->render_height = 0u;
renderer->bound_pipe = VK_NULL_HANDLE;
- vkCmdEndRenderPass(render_cb->vk);
-
// insert acquire and release barriers for dmabuf-images
unsigned barrier_count = wl_list_length(&renderer->foreign_textures) + 1;
VkImageMemoryBarrier *acquire_barriers = calloc(barrier_count, sizeof(VkImageMemoryBarrier));
@@ -1001,9 +1192,9 @@ static void vulkan_end(struct wlr_renderer *wlr_renderer) {
// also add acquire/release barriers for the current render buffer
VkImageLayout src_layout = VK_IMAGE_LAYOUT_GENERAL;
- if (!renderer->current_render_buffer->transitioned) {
+ if (!current_rb->transitioned) {
src_layout = VK_IMAGE_LAYOUT_PREINITIALIZED;
- renderer->current_render_buffer->transitioned = true;
+ current_rb->transitioned = true;
}
// acquire render buffer before rendering
@@ -1040,6 +1231,36 @@ static void vulkan_end(struct wlr_renderer *wlr_renderer) {
++idx;
+ if (current_rb->blend_image) {
+ // The render pass changes the blend image layout from
+ // color attachment to read only, so on each frame, before
+ // the render pass starts, we change it back
+ VkImageLayout blend_src_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ if (!current_rb->blend_transitioned) {
+ blend_src_layout = VK_IMAGE_LAYOUT_UNDEFINED;
+ current_rb->blend_transitioned = true;
+ }
+
+ VkImageMemoryBarrier blend_acq_barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = current_rb->blend_image,
+ .oldLayout = blend_src_layout,
+ .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .srcAccessMask = VK_ACCESS_SHADER_READ_BIT,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .layerCount = 1,
+ .levelCount = 1,
+ }
+ };
+ vkCmdPipelineBarrier(stage_cb->vk, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ 0, 0, NULL, 0, NULL, 1, &blend_acq_barrier);
+ }
+
vkCmdPipelineBarrier(stage_cb->vk, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0, 0, NULL, 0, NULL, barrier_count, acquire_barriers);
@@ -1407,14 +1628,21 @@ static void vulkan_destroy(struct wlr_renderer *wlr_renderer) {
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);
vkDestroySemaphore(dev->dev, renderer->timeline_semaphore, NULL);
vkDestroyPipelineLayout(dev->dev, renderer->pipe_layout, NULL);
vkDestroyDescriptorSetLayout(dev->dev, renderer->ds_layout, NULL);
+ vkDestroyPipelineLayout(dev->dev, renderer->output_pipe_layout, NULL);
+ vkDestroyDescriptorSetLayout(dev->dev, renderer->output_ds_layout, NULL);
vkDestroySampler(dev->dev, renderer->sampler, NULL);
vkDestroySamplerYcbcrConversion(dev->dev, renderer->nv12_conversion, NULL);
vkDestroyCommandPool(dev->dev, renderer->command_pool, NULL);
@@ -1786,6 +2014,58 @@ static bool init_tex_layouts(struct wlr_vk_renderer *renderer,
return true;
}
+static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer,
+ VkDescriptorSetLayout *out_ds_layout,
+ VkPipelineLayout *out_pipe_layout) {
+ VkResult res;
+ VkDevice dev = renderer->dev->dev;
+
+ // layouts, descriptor set
+ VkDescriptorSetLayoutBinding ds_binding = {
+ .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,
+ };
+
+ res = vkCreateDescriptorSetLayout(dev, &ds_info, NULL, out_ds_layout);
+ if (res != VK_SUCCESS) {
+ wlr_vk_error("vkCreateDescriptorSetLayout", res);
+ return false;
+ }
+
+ // pipeline layout -- standard vertex uniforms, no shader uniforms
+ VkPushConstantRange pc_ranges[1] = {
+ {
+ .size = sizeof(struct vert_pcr_data),
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ },
+ };
+
+ VkPipelineLayoutCreateInfo pl_info = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .setLayoutCount = 1,
+ .pSetLayouts = out_ds_layout,
+ .pushConstantRangeCount = 1,
+ .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;
+}
+
// Initializes the pipeline for rendering textures and using the given
// VkRenderPass and VkPipelineLayout.
static bool init_tex_pipeline(struct wlr_vk_renderer *renderer,
@@ -1916,6 +2196,110 @@ static bool init_tex_pipeline(struct wlr_vk_renderer *renderer,
return true;
}
+static bool init_blend_to_output_pipeline(struct wlr_vk_renderer *renderer,
+ VkRenderPass rp, VkPipelineLayout pipe_layout, VkPipeline *pipe) {
+ VkResult res;
+ VkDevice dev = renderer->dev->dev;
+
+ // shaders
+ VkPipelineShaderStageCreateInfo tex_stages[2] = {
+ {
+ .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",
+ },
+ };
+
+ // info
+ 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 dynStates[2] = {
+ VK_DYNAMIC_STATE_VIEWPORT,
+ VK_DYNAMIC_STATE_SCISSOR,
+ };
+ VkPipelineDynamicStateCreateInfo dynamic = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .pDynamicStates = dynStates,
+ .dynamicStateCount = 2,
+ };
+
+ VkPipelineVertexInputStateCreateInfo vertex = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ };
+
+ VkGraphicsPipelineCreateInfo pinfo = {
+ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = NULL,
+ .layout = pipe_layout,
+ .renderPass = rp,
+ .subpass = 1, // second subpass!
+ .stageCount = 2,
+ .pStages = tex_stages,
+ .pInputAssemblyState = &assembly,
+ .pRasterizationState = &rasterization,
+ .pColorBlendState = &blend,
+ .pMultisampleState = &multisample,
+ .pViewportState = &viewport,
+ .pDynamicState = &dynamic,
+ .pVertexInputState = &vertex,
+ };
+
+ // NOTE: use could use a cache here for faster loading
+ // store it somewhere like $XDG_CACHE_HOME/wlroots/vk_pipe_cache
+ 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;
+}
+
// Creates static render data, such as sampler, layouts and shader modules
// for the given rednerer.
// Cleanup is done by destroying the renderer.
@@ -1957,6 +2341,11 @@ static bool init_static_render_data(struct wlr_vk_renderer *renderer) {
return false;
}
+ if (!init_blend_to_output_layouts(renderer, &renderer->output_ds_layout,
+ &renderer->output_pipe_layout)) {
+ return false;
+ }
+
// load vert module and tex frag module since they are needed to
// initialize the tex pipeline
VkShaderModuleCreateInfo sinfo = {
@@ -1994,11 +2383,23 @@ static bool init_static_render_data(struct wlr_vk_renderer *renderer) {
return false;
}
+ // quad frag
+ 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, VkFormat format) {
+ struct wlr_vk_renderer *renderer, VkFormat format, bool has_blending_buffer) {
struct wlr_vk_render_format_setup *setup;
wl_list_for_each(setup, &renderer->render_format_setups, link) {
if (setup->render_format == format) {
@@ -2018,72 +2419,189 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup(
VkDevice dev = renderer->dev->dev;
VkResult res;
- VkAttachmentDescription attachment = {
- .format = format,
- .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 (has_blending_buffer) {
+ VkAttachmentDescription attachments[2] = {
+ {
+ .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,
+ .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 color_ref = {
- .attachment = 0u,
- .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
- };
+ VkAttachmentReference blend_write_ref = {
+ .attachment = 0u,
+ .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ };
- VkSubpassDescription subpass = {
- .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
- .colorAttachmentCount = 1,
- .pColorAttachments = &color_ref,
- };
+ VkAttachmentReference blend_read_ref = {
+ .attachment = 0u,
+ .layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+ };
- VkSubpassDependency deps[2] = {
- {
- .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,
- },
- };
+ VkAttachmentReference color_ref = {
+ .attachment = 1u,
+ .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ };
- VkRenderPassCreateInfo rp_info = {
- .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
- .attachmentCount = 1,
- .pAttachments = &attachment,
- .subpassCount = 1,
- .pSubpasses = &subpass,
- .dependencyCount = 2u,
- .pDependencies = deps,
- };
+ VkSubpassDescription subpasses[2] = {
+ {
+ .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,
+ }
+ };
- res = vkCreateRenderPass(dev, &rp_info, NULL, &setup->render_pass);
- if (res != VK_SUCCESS) {
- wlr_vk_error("Failed to create render pass", res);
- free(setup);
- return NULL;
+ VkSubpassDependency deps[3] = {
+ {
+ .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 = 2u,
+ .pAttachments = attachments,
+ .subpassCount = 2u,
+ .pSubpasses = subpasses,
+ .dependencyCount = 3u,
+ .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;
+ }
+
+ // 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)) {
+ goto error;
+ }
+ } else {
+ VkAttachmentDescription attachment = {
+ .format = format,
+ .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 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[2] = {
+ {
+ .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 = 2u,
+ .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 (!init_tex_pipeline(renderer, setup->render_pass, renderer->pipe_layout,
@@ -2225,6 +2743,7 @@ struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev
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);
diff --git a/render/vulkan/shaders/meson.build b/render/vulkan/shaders/meson.build
index 906618c2..50f4a1f0 100644
--- a/render/vulkan/shaders/meson.build
+++ b/render/vulkan/shaders/meson.build
@@ -2,6 +2,7 @@ vulkan_shaders_src = [
'common.vert',
'texture.frag',
'quad.frag',
+ 'output.frag',
]
vulkan_shaders = []
diff --git a/render/vulkan/shaders/output.frag b/render/vulkan/shaders/output.frag
new file mode 100644
index 00000000..28a8cdc5
--- /dev/null
+++ b/render/vulkan/shaders/output.frag
@@ -0,0 +1,21 @@
+#version 450
+
+layout (input_attachment_index = 0, binding = 0) uniform subpassInput in_color;
+
+layout(location = 0) in vec2 uv;
+layout(location = 0) out vec4 out_color;
+
+float linear_to_srgb(float x) {
+ return max(min(x * 12.92, 0.04045), 1.055 * pow(x, 1. / 2.4) - 0.055);
+}
+
+void main() {
+ vec4 val = subpassLoad(in_color).rgba;
+ out_color = vec4(
+ linear_to_srgb(val.r),
+ linear_to_srgb(val.g),
+ linear_to_srgb(val.b),
+ val.a
+ );
+}
+