diff --git a/include/render/color.h b/include/render/color.h index 4ab587be1..e18177c38 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -7,6 +7,7 @@ enum wlr_color_transform_type { COLOR_TRANSFORM_SRGB, COLOR_TRANSFORM_LUT_3D, + COLOR_TRANSFORM_LUT_3x1D, }; struct wlr_color_transform { @@ -37,6 +38,36 @@ struct wlr_color_transform_lut3d { size_t dim_len; }; +/** + * This is a color transform that is specified by three seperate ramps that + * modify the RGB values individually. This means that this color transform type + * cannot be used to create a transform that can infruence color channels + * depending on the values of other color channels. + * + * This color transform is modeled off of the wlr-gamma-control-unstable-v1 + * wayland protocol. + * + * The memory layout of this type requires that the r, g, b color channel ramps + * are inline in memory. A ramp value can be retrieved from memory: + * + * offset = dim_len * rgb_channel_index + ramp_index + * + * This is an offset into the `r` pointer and can be used to retrieve the ramps + * from the other channels as the ramps are linear in memory. The three pointers + * are given for convenience. + * + * Note that when freeing the color transform, only the `r` channel is freed as + * a it's expected that a single malloc will allocate all three channels at once. + */ +struct wlr_color_transform_lut3x1d { + struct wlr_color_transform base; + + uint16_t *r; + uint16_t *g; + uint16_t *b; + size_t ramp_size; +}; + /** * Gets a wlr_color_transform_lut3d from a generic wlr_color_transform. * Asserts that the base type is COLOR_TRANSFORM_LUT_3D @@ -44,4 +75,11 @@ struct wlr_color_transform_lut3d { struct wlr_color_transform_lut3d *wlr_color_transform_lut3d_from_base( struct wlr_color_transform *tr); +/** + * Gets a wlr_color_transform_lut3x1d from a generic wlr_color_transform. + * Asserts that the base type is COLOR_TRANSFORM_LUT_3x1D + */ +struct wlr_color_transform_lut3x1d *wlr_color_transform_lut3x1d_from_base( + struct wlr_color_transform *tr); + #endif diff --git a/include/render/vulkan.h b/include/render/vulkan.h index abeb11cc5..e6e0d3216 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -163,6 +163,7 @@ enum wlr_vk_shader_source { enum wlr_vk_output_transform { WLR_VK_OUTPUT_TRANSFORM_INVERSE_SRGB = 0, WLR_VK_OUTPUT_TRANSFORM_LUT3D = 1, + WLR_VK_OUTPUT_TRANSFORM_LUT3x1D = 2, }; struct wlr_vk_pipeline_key { @@ -193,6 +194,7 @@ struct wlr_vk_render_format_setup { VkPipeline output_pipe_srgb; VkPipeline output_pipe_lut3d; + VkPipeline output_pipe_lut3x1d; struct wlr_vk_renderer *renderer; struct wl_list pipelines; // struct wlr_vk_pipeline.link @@ -271,7 +273,9 @@ struct wlr_vk_renderer { VkShaderModule vert_module; VkShaderModule tex_frag_module; VkShaderModule quad_frag_module; - VkShaderModule output_module; + VkShaderModule output_module_srgb; + VkShaderModule output_module_3d_lut; + VkShaderModule output_module_3x1d_lut; struct wl_list pipeline_layouts; // struct wlr_vk_pipeline_layout.link @@ -280,19 +284,10 @@ struct wlr_vk_renderer { VkDescriptorSetLayout output_ds_srgb_layout; VkDescriptorSetLayout output_ds_lut3d_layout; VkSampler output_sampler_lut3d; - // descriptor set indicating dummy 1x1x1 image, for use in the lut3d slot - VkDescriptorSet output_ds_lut3d_dummy; - struct wlr_vk_descriptor_pool *output_ds_lut3d_dummy_pool; size_t last_output_pool_size; struct wl_list output_descriptor_pools; // wlr_vk_descriptor_pool.link - // dummy sampler to bind when output shader is not using a lookup table - VkImage dummy3d_image; - VkDeviceMemory dummy3d_mem; - VkImageView dummy3d_image_view; - bool dummy3d_image_transitioned; - VkSemaphore timeline_semaphore; uint64_t timeline_point; @@ -521,7 +516,7 @@ struct wlr_vk_color_transform { VkDeviceMemory memory; VkDescriptorSet ds; struct wlr_vk_descriptor_pool *ds_pool; - } lut_3d; + } lut; }; void vk_color_transform_destroy(struct wlr_addon *addon); diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index 43d9154be..619e07acf 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -10,6 +10,7 @@ #define WLR_RENDER_COLOR_H #include +#include #include /** @@ -52,4 +53,10 @@ struct wlr_color_transform *wlr_color_transform_ref(struct wlr_color_transform * */ void wlr_color_transform_unref(struct wlr_color_transform *tr); +/** + * Creates a color transform based on a gamma ramp. + */ +struct wlr_color_transform *wlr_color_transform_create_from_gamma_lut( + size_t ramp_size, const uint16_t *r, const uint16_t *g, const uint16_t *b); + #endif diff --git a/render/color.c b/render/color.c index 9f8d76b06..d7b4d7691 100644 --- a/render/color.c +++ b/render/color.c @@ -1,5 +1,6 @@ #include #include +#include #include #include "render/color.h" @@ -23,6 +24,11 @@ static void color_transform_destroy(struct wlr_color_transform *tr) { wlr_color_transform_lut3d_from_base(tr); free(lut3d->lut_3d); break; + case COLOR_TRANSFORM_LUT_3x1D:; + struct wlr_color_transform_lut3x1d *lut3x1d = + wlr_color_transform_lut3x1d_from_base(tr); + free(lut3x1d->r); + break; } wlr_addon_set_finish(&tr->addons); free(tr); @@ -44,9 +50,45 @@ void wlr_color_transform_unref(struct wlr_color_transform *tr) { } } +struct wlr_color_transform *wlr_color_transform_create_from_gamma_lut( + size_t ramp_size, const uint16_t *r, const uint16_t *g, const uint16_t *b) { + uint16_t *data = malloc(3 * ramp_size * sizeof(uint16_t)); + if (!data) { + return NULL; + } + + struct wlr_color_transform_lut3x1d *tx = calloc(1, sizeof(*tx)); + if (!tx) { + free(data); + return NULL; + } + + tx->base.type = COLOR_TRANSFORM_LUT_3x1D; + tx->base.ref_count = 1; + wlr_addon_set_init(&tx->base.addons); + + tx->r = data; + tx->g = data + ramp_size; + tx->b = data + ramp_size * 2; + tx->ramp_size = ramp_size; + + memcpy(tx->r, r, ramp_size * sizeof(uint16_t)); + memcpy(tx->g, g, ramp_size * sizeof(uint16_t)); + memcpy(tx->b, b, ramp_size * sizeof(uint16_t)); + + return &tx->base; +} + struct wlr_color_transform_lut3d *wlr_color_transform_lut3d_from_base( struct wlr_color_transform *tr) { assert(tr->type == COLOR_TRANSFORM_LUT_3D); struct wlr_color_transform_lut3d *lut3d = wl_container_of(tr, lut3d, base); return lut3d; } + +struct wlr_color_transform_lut3x1d *wlr_color_transform_lut3x1d_from_base( + struct wlr_color_transform *tr) { + assert(tr->type == COLOR_TRANSFORM_LUT_3x1D); + struct wlr_color_transform_lut3x1d *lut = wl_container_of(tr, lut, base); + return lut; +} diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 032fcc7b2..60c1e234a 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -179,44 +179,58 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { .uv_size = { 1, 1 }, }; - size_t dim = 1; - if (pass->color_transform && pass->color_transform->type == COLOR_TRANSFORM_LUT_3D) { - struct wlr_color_transform_lut3d *lut3d = - wlr_color_transform_lut3d_from_base(pass->color_transform); - dim = lut3d->dim_len; - } - - struct wlr_vk_frag_output_pcr_data frag_pcr_data = { - .lut_3d_offset = 0.5f / dim, - .lut_3d_scale = (float)(dim - 1) / dim, - }; mat3_to_mat4(final_matrix, vert_pcr_data.mat4); - - if (pass->color_transform) { - bind_pipeline(pass, render_buffer->plain.render_setup->output_pipe_lut3d); - } else { - bind_pipeline(pass, render_buffer->plain.render_setup->output_pipe_srgb); - } vkCmdPushConstants(render_cb->vk, renderer->output_pipe_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(vert_pcr_data), &vert_pcr_data); - vkCmdPushConstants(render_cb->vk, renderer->output_pipe_layout, - VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(vert_pcr_data), - sizeof(frag_pcr_data), &frag_pcr_data); - VkDescriptorSet lut_ds; - if (pass->color_transform && pass->color_transform->type == COLOR_TRANSFORM_LUT_3D) { - struct wlr_vk_color_transform *transform = - get_color_transform(pass->color_transform, renderer); - assert(transform); - lut_ds = transform->lut_3d.ds; - } else { - lut_ds = renderer->output_ds_lut3d_dummy; + VkDescriptorSet ds[2]; + size_t ds_len = 0; + ds[ds_len++] = render_buffer->plain.blend_descriptor_set; + + VkPipeline pl = render_buffer->plain.render_setup->output_pipe_srgb; + if (pass->color_transform) { + switch (pass->color_transform->type){ + case COLOR_TRANSFORM_LUT_3D: { + pl = render_buffer->plain.render_setup->output_pipe_lut3d; + struct wlr_vk_color_transform *transform = + get_color_transform(pass->color_transform, renderer); + ds[ds_len++] = transform->lut.ds; + + struct wlr_color_transform_lut3d *lut = + wlr_color_transform_lut3d_from_base(pass->color_transform); + struct wlr_vk_frag_output_pcr_data frag_pcr_data = { + .lut_3d_offset = 0.5f / lut->dim_len, + .lut_3d_scale = (float)(lut->dim_len - 1) / lut->dim_len, + }; + vkCmdPushConstants(render_cb->vk, renderer->output_pipe_layout, + VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(vert_pcr_data), + sizeof(frag_pcr_data), &frag_pcr_data); + + break; + } + case COLOR_TRANSFORM_LUT_3x1D: { + pl = render_buffer->plain.render_setup->output_pipe_lut3x1d; + struct wlr_vk_color_transform *transform = + get_color_transform(pass->color_transform, renderer); + ds[ds_len++] = transform->lut.ds; + + struct wlr_color_transform_lut3x1d *lut = + wlr_color_transform_lut3x1d_from_base(pass->color_transform); + struct wlr_vk_frag_output_pcr_data frag_pcr_data = { + .lut_3d_offset = 0.5f / lut->ramp_size, + .lut_3d_scale = (float)(lut->ramp_size - 1) / lut->ramp_size, + }; + vkCmdPushConstants(render_cb->vk, renderer->output_pipe_layout, + VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(vert_pcr_data), + sizeof(frag_pcr_data), &frag_pcr_data); + break; + } + case COLOR_TRANSFORM_SRGB: + break; + } } - VkDescriptorSet ds[] = { - render_buffer->plain.blend_descriptor_set, // set 0 - lut_ds, // set 1 - }; - size_t ds_len = sizeof(ds) / sizeof(ds[0]); + + bind_pipeline(pass, pl); vkCmdBindDescriptorSets(render_cb->vk, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->output_pipe_layout, 0, ds_len, ds, 0, NULL); @@ -827,11 +841,11 @@ void vk_color_transform_destroy(struct wlr_addon *addon) { struct wlr_vk_color_transform *transform = wl_container_of(addon, transform, addon); VkDevice dev = renderer->dev->dev; - if (transform->lut_3d.image) { - vkDestroyImage(dev, transform->lut_3d.image, NULL); - vkDestroyImageView(dev, transform->lut_3d.image_view, NULL); - vkFreeMemory(dev, transform->lut_3d.memory, NULL); - vulkan_free_ds(renderer, transform->lut_3d.ds_pool, transform->lut_3d.ds); + if (transform->lut.image) { + vkDestroyImage(dev, transform->lut.image, NULL); + vkDestroyImageView(dev, transform->lut.image_view, NULL); + vkFreeMemory(dev, transform->lut.memory, NULL); + vulkan_free_ds(renderer, transform->lut.ds_pool, transform->lut.ds); } wl_list_remove(&transform->link); @@ -1004,6 +1018,166 @@ fail_image: return false; } +static bool create_3x1d_lut_image(struct wlr_vk_renderer *renderer, + const struct wlr_color_transform_lut3x1d *lut, + VkImage *image, VkImageView *image_view, + VkDeviceMemory *memory, VkDescriptorSet *ds, + struct wlr_vk_descriptor_pool **ds_pool) { + VkDevice dev = renderer->dev->dev; + VkResult res; + + *image = VK_NULL_HANDLE; + *memory = VK_NULL_HANDLE; + *image_view = VK_NULL_HANDLE; + *ds = VK_NULL_HANDLE; + *ds_pool = NULL; + + // R32G32B32 is not a required Vulkan format + // TODO: use it when available + VkFormat format = VK_FORMAT_R32G32B32A32_SFLOAT; + + VkImageCreateInfo img_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_1D, + .format = format, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .extent = (VkExtent3D) { lut->ramp_size, 1, 1 }, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + }; + res = vkCreateImage(dev, &img_info, NULL, image); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateImage failed", res); + return NULL; + } + + VkMemoryRequirements mem_reqs = {0}; + vkGetImageMemoryRequirements(dev, *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"); + goto fail_image; + } + + VkMemoryAllocateInfo mem_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = mem_reqs.size, + .memoryTypeIndex = mem_type_index, + }; + res = vkAllocateMemory(dev, &mem_info, NULL, memory); + if (res != VK_SUCCESS) { + wlr_vk_error("vkAllocateMemory failed", res); + goto fail_image; + } + + res = vkBindImageMemory(dev, *image, *memory, 0); + if (res != VK_SUCCESS) { + wlr_vk_error("vkBindMemory failed", res); + goto fail_memory; + } + + VkImageViewCreateInfo view_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .viewType = VK_IMAGE_VIEW_TYPE_1D, + .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 = *image, + }; + res = vkCreateImageView(dev, &view_info, NULL, image_view); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateImageView failed", res); + goto fail_image; + } + + size_t bytes_per_block = 4 * sizeof(float); + size_t size = lut->ramp_size * bytes_per_block; + struct wlr_vk_buffer_span span = vulkan_get_stage_span(renderer, + size, bytes_per_block); + if (!span.buffer || span.alloc.size != size) { + wlr_log(WLR_ERROR, "Failed to retrieve staging buffer"); + goto fail_imageview; + } + + char *map = (char*)span.buffer->cpu_mapping + span.alloc.start; + float *dst = (float*)map; + + float normalize = (1 << 16) - 1; + for (size_t i = 0; i < lut->ramp_size; i++) { + size_t dst_offset = 4 * i; + dst[dst_offset + 0] = (float)lut->r[i] / normalize; + dst[dst_offset + 1] = (float)lut->g[i] / normalize; + dst[dst_offset + 2] = (float)lut->b[i] / normalize; + dst[dst_offset + 3] = 1.0; + } + + VkCommandBuffer cb = vulkan_record_stage_cb(renderer); + vulkan_change_layout(cb, *image, + VK_IMAGE_LAYOUT_UNDEFINED, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT); + VkBufferImageCopy copy = { + .bufferOffset = span.alloc.start, + .imageExtent.width = lut->ramp_size, + .imageExtent.height = 1, + .imageExtent.depth = 1, + .imageSubresource.layerCount = 1, + .imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + }; + vkCmdCopyBufferToImage(cb, span.buffer->buffer, *image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); + vulkan_change_layout(cb, *image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_SHADER_READ_BIT); + + *ds_pool = vulkan_alloc_texture_ds(renderer, + renderer->output_ds_lut3d_layout, ds); + if (!*ds_pool) { + wlr_log(WLR_ERROR, "Failed to allocate descriptor"); + goto fail_imageview; + } + + VkDescriptorImageInfo ds_img_info = { + .imageView = *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 = *ds, + .pImageInfo = &ds_img_info, + }; + vkUpdateDescriptorSets(dev, 1, &ds_write, 0, NULL); + + return true; + +fail_imageview: + vkDestroyImageView(dev, *image_view, NULL); +fail_memory: + vkFreeMemory(dev, *memory, NULL); +fail_image: + vkDestroyImage(dev, *image, NULL); + return false; +} + static struct wlr_vk_color_transform *vk_color_transform_create( struct wlr_vk_renderer *renderer, struct wlr_color_transform *transform) { struct wlr_vk_color_transform *vk_transform = @@ -1012,17 +1186,33 @@ static struct wlr_vk_color_transform *vk_color_transform_create( return NULL; } - if (transform->type == COLOR_TRANSFORM_LUT_3D) { + switch(transform->type) { + case COLOR_TRANSFORM_LUT_3D: if (!create_3d_lut_image(renderer, wlr_color_transform_lut3d_from_base(transform), - &vk_transform->lut_3d.image, - &vk_transform->lut_3d.image_view, - &vk_transform->lut_3d.memory, - &vk_transform->lut_3d.ds, - &vk_transform->lut_3d.ds_pool)) { + &vk_transform->lut.image, + &vk_transform->lut.image_view, + &vk_transform->lut.memory, + &vk_transform->lut.ds, + &vk_transform->lut.ds_pool)) { free(vk_transform); return NULL; } + break; + case COLOR_TRANSFORM_LUT_3x1D: + if (!create_3x1d_lut_image(renderer, + wlr_color_transform_lut3x1d_from_base(transform), + &vk_transform->lut.image, + &vk_transform->lut.image_view, + &vk_transform->lut.memory, + &vk_transform->lut.ds, + &vk_transform->lut.ds_pool)) { + free(vk_transform); + return NULL; + } + break; + case COLOR_TRANSFORM_SRGB: + break; } wlr_addon_init(&vk_transform->addon, &transform->addons, @@ -1100,14 +1290,6 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend return NULL; } - if (!renderer->dummy3d_image_transitioned) { - renderer->dummy3d_image_transitioned = true; - vulkan_change_layout(cb->vk, renderer->dummy3d_image, - VK_IMAGE_LAYOUT_UNDEFINED, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - 0, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_SHADER_READ_BIT); - } - int width = buffer->wlr_buffer->width; int height = buffer->wlr_buffer->height; VkRect2D rect = { .extent = { width, height } }; diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 4051dbf05..c75df4cd1 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -25,7 +25,9 @@ #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 "render/vulkan/shaders/output_inverse_rgb.frag.h" +#include "render/vulkan/shaders/output_lut_3d.frag.h" +#include "render/vulkan/shaders/output_lut_3x1d.frag.h" #include "types/wlr_buffer.h" #include "util/time.h" @@ -1120,7 +1122,9 @@ static void vulkan_destroy(struct wlr_renderer *wlr_renderer) { 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); + vkDestroyShaderModule(dev->dev, renderer->output_module_srgb, NULL); + vkDestroyShaderModule(dev->dev, renderer->output_module_3d_lut, NULL); + vkDestroyShaderModule(dev->dev, renderer->output_module_3x1d_lut, NULL); struct wlr_vk_pipeline_layout *pipeline_layout, *pipeline_layout_tmp; wl_list_for_each_safe(pipeline_layout, pipeline_layout_tmp, @@ -1132,10 +1136,6 @@ static void vulkan_destroy(struct wlr_renderer *wlr_renderer) { 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); @@ -1800,18 +1800,18 @@ static bool init_blend_to_output_pipeline(struct wlr_vk_renderer *renderer, 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, - }; + VkShaderModule output_module; + switch(transform) { + case WLR_VK_OUTPUT_TRANSFORM_INVERSE_SRGB: + output_module = renderer->output_module_srgb; + break; + case WLR_VK_OUTPUT_TRANSFORM_LUT3D: + output_module = renderer->output_module_3d_lut; + break; + case WLR_VK_OUTPUT_TRANSFORM_LUT3x1D: + output_module = renderer->output_module_3x1d_lut; + break; + } VkPipelineShaderStageCreateInfo tex_stages[2] = { { @@ -1823,9 +1823,8 @@ static bool init_blend_to_output_pipeline(struct wlr_vk_renderer *renderer, { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, - .module = renderer->output_module, + .module = output_module, .pName = "main", - .pSpecializationInfo = &specialization, }, }; @@ -1992,105 +1991,6 @@ struct wlr_vk_pipeline_layout *get_or_create_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. @@ -2102,10 +2002,6 @@ static bool init_static_render_data(struct wlr_vk_renderer *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 = { @@ -2143,12 +2039,37 @@ static bool init_static_render_data(struct wlr_vk_renderer *renderer) { sinfo = (VkShaderModuleCreateInfo){ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, - .codeSize = sizeof(output_frag_data), - .pCode = output_frag_data, + .codeSize = sizeof(output_inverse_rgb_data), + .pCode = output_inverse_rgb_data, }; - res = vkCreateShaderModule(dev, &sinfo, NULL, &renderer->output_module); + + res = vkCreateShaderModule(dev, &sinfo, NULL, &renderer->output_module_srgb); if (res != VK_SUCCESS) { - wlr_vk_error("Failed to create blend->output fragment shader module", res); + wlr_vk_error("Failed to create blend->output fragment shader module for srgb", res); + return false; + } + + sinfo = (VkShaderModuleCreateInfo){ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = sizeof(output_lut_3d_data), + .pCode = output_lut_3d_data, + }; + + res = vkCreateShaderModule(dev, &sinfo, NULL, &renderer->output_module_3d_lut); + if (res != VK_SUCCESS) { + wlr_vk_error("Failed to create blend->output fragment shader module for 3d lut", res); + return false; + } + + sinfo = (VkShaderModuleCreateInfo){ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = sizeof(output_lut_3x1d_data), + .pCode = output_lut_3x1d_data, + }; + + res = vkCreateShaderModule(dev, &sinfo, NULL, &renderer->output_module_3x1d_lut); + if (res != VK_SUCCESS) { + wlr_vk_error("Failed to create blend->output fragment shader module for 3x1d lut", res); return false; } @@ -2301,6 +2222,11 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( &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_lut3x1d, WLR_VK_OUTPUT_TRANSFORM_LUT3x1D)) { + goto error; + } } else { assert(format->vk_srgb); VkAttachmentDescription attachment = { diff --git a/render/vulkan/shaders/meson.build b/render/vulkan/shaders/meson.build index 50f4a1f0d..81db20019 100644 --- a/render/vulkan/shaders/meson.build +++ b/render/vulkan/shaders/meson.build @@ -2,7 +2,6 @@ vulkan_shaders_src = [ 'common.vert', 'texture.frag', 'quad.frag', - 'output.frag', ] vulkan_shaders = [] @@ -21,4 +20,25 @@ foreach shader : vulkan_shaders_src vulkan_shaders += [header] endforeach +vulkan_shader_output_color_transforms = { + 'inverse_rgb': '0', + 'lut_3d': '1', + 'lut_3x1d': '2', +} + +foreach name, ident : vulkan_shader_output_color_transforms + name = 'output_' + name + args = [glslang, '-DOUTPUT_TRANSFORM=' + ident, '-V', '@INPUT@', '-o', '@OUTPUT@', '--vn', name + '_data'] + if glslang_version.version_compare('>=11.0.0') + args += '--quiet' + endif + header = custom_target( + name + '.frag_spv', + output: name + '.frag.h', + input: 'output.frag', + command: args) + + vulkan_shaders += [header] +endforeach + wlr_files += vulkan_shaders diff --git a/render/vulkan/shaders/output.frag b/render/vulkan/shaders/output.frag index 2a744aa24..56fea0f09 100644 --- a/render/vulkan/shaders/output.frag +++ b/render/vulkan/shaders/output.frag @@ -2,59 +2,67 @@ layout (input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput in_color; -layout(set = 1, binding = 0) uniform sampler3D lut_3d; +// Matches enum wlr_vk_output_transform +#define OUTPUT_TRANSFORM_INVERSE_SRGB 0 +#define OUTPUT_TRANSFORM_LUT_3D 1 +#define OUTPUT_TRANSFORM_LUT_3x1D 2 layout(location = 0) in vec2 uv; layout(location = 0) out vec4 out_color; -/* struct wlr_vk_frag_output_pcr_data */ -layout(push_constant) uniform UBO { - layout(offset = 80) float lut_3d_offset; - float lut_3d_scale; -} data; +#if OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_LUT_3x1D + layout(set = 1, binding = 0) uniform sampler1D lut_3x1d; +#endif -layout (constant_id = 0) const int OUTPUT_TRANSFORM = 0; +#if OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_LUT_3D + layout(set = 1, binding = 0) uniform sampler3D lut_3d; +#endif -// Matches enum wlr_vk_output_transform -#define OUTPUT_TRANSFORM_INVERSE_SRGB 0 -#define OUTPUT_TRANSFORM_LUT_3D 1 +#if OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_LUT_3D || OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_LUT_3x1D + /* struct wlr_vk_frag_output_pcr_data */ + layout(push_constant) uniform UBO { + layout(offset = 80) float lut_3d_offset; + float lut_3d_scale; + } data; +#endif float linear_channel_to_srgb(float x) { return max(min(x * 12.92, 0.04045), 1.055 * pow(x, 1. / 2.4) - 0.055); } -vec4 linear_color_to_srgb(vec4 color) { - if (color.a == 0) { - return vec4(0); - } - color.rgb /= color.a; - color.rgb = vec3( - linear_channel_to_srgb(color.r), - linear_channel_to_srgb(color.g), - linear_channel_to_srgb(color.b) - ); - color.rgb *= color.a; - return color; -} - void main() { vec4 val = subpassLoad(in_color).rgba; - if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_LUT_3D) { - if (val.a == 0) { - out_color = vec4(0); - return; - } - // Convert from pre-multiplied alpha to straight alpha - vec3 rgb = val.rgb / val.a; - - // Apply 3D LUT - vec3 pos = data.lut_3d_offset + rgb * data.lut_3d_scale; - rgb = texture(lut_3d, pos).rgb; - - // Back to pre-multiplied alpha - out_color = vec4(rgb * val.a, val.a); - } else { // OUTPUT_TRANSFORM_INVERSE_SRGB - // Produce post-premultiplied sRGB encoded values - out_color = linear_color_to_srgb(val); + if (val.a == 0) { + out_color = vec4(0); + return; } + + // Convert from pre-multiplied alpha to straight alpha + vec3 rgb = val.rgb / val.a; + +#if OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_LUT_3D + // Apply 3D LUT + vec3 pos = data.lut_3d_offset + rgb * data.lut_3d_scale; + rgb = texture(lut_3d, pos).rgb; +#endif + +#if OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_LUT_3x1D + vec3 pos = data.lut_3d_offset + rgb * data.lut_3d_scale; + rgb = vec3( + texture(lut_3x1d, pos.r).r, + texture(lut_3x1d, pos.g).g, + texture(lut_3x1d, pos.b).b + ); +#endif + +#if OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_INVERSE_SRGB + rgb = vec3( + linear_channel_to_srgb(rgb.r), + linear_channel_to_srgb(rgb.g), + linear_channel_to_srgb(rgb.b) + ); +#endif + + // Back to pre-multiplied alpha + out_color = vec4(rgb * val.a, val.a); }