From 611e0649c50f625488b9136923f0d0bbab8917ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Thu, 5 Feb 2026 20:14:53 +0000 Subject: [PATCH] render/vulkan: 3D LUT fallback for texture pass color transform --- include/render/vulkan.h | 20 +++-- render/vulkan/pass.c | 117 +++++++++++++++++++++-------- render/vulkan/renderer.c | 47 ++++++++---- render/vulkan/shaders/texture.frag | 9 +++ 4 files changed, 140 insertions(+), 53 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 889d60d8a..b3624bb61 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -168,6 +168,7 @@ enum wlr_vk_texture_transform { WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ = 2, WLR_VK_TEXTURE_TRANSFORM_GAMMA22 = 3, WLR_VK_TEXTURE_TRANSFORM_BT1886 = 4, + WLR_VK_TEXTURE_TRANSFORM_LUT_3D = 5, }; enum wlr_vk_shader_source { @@ -286,8 +287,8 @@ struct wlr_vk_command_buffer { struct wl_list destroy_textures; // wlr_vk_texture.destroy_link // Staging shared buffers to release after the command buffer completes struct wl_list stage_buffers; // wlr_vk_shared_buffer.link - // Color transform to unref after the command buffer completes - struct wlr_color_transform *color_transform; + // Color transforms to unref after the command buffer completes + struct wl_array color_transforms; // struct wlr_color_transform* // For DMA-BUF implicit sync interop, may be NULL VkSemaphore binary_semaphore; @@ -315,11 +316,12 @@ struct wlr_vk_renderer { // for blend->output subpass VkPipelineLayout output_pipe_layout; VkDescriptorSetLayout output_ds_srgb_layout; - VkDescriptorSetLayout output_ds_lut3d_layout; - VkSampler output_sampler_lut3d; + + VkDescriptorSetLayout ds_lut3d_layout; + VkSampler 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; + VkDescriptorSet ds_lut3d_dummy; + struct wlr_vk_descriptor_pool *ds_lut3d_dummy_pool; size_t last_output_pool_size; struct wl_list output_descriptor_pools; // wlr_vk_descriptor_pool.link @@ -374,6 +376,8 @@ struct wlr_vk_vert_pcr_data { struct wlr_vk_frag_texture_pcr_data { float matrix[4][4]; // only a 3x3 subset is used float alpha; + float lut_3d_offset; + float lut_3d_scale; }; struct wlr_vk_frag_output_pcr_data { @@ -488,6 +492,8 @@ bool vulkan_wait_command_buffer(struct wlr_vk_command_buffer *cb, struct wlr_vk_renderer *renderer); 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); +bool vulkan_command_buffer_ref_color_transform(struct wlr_vk_command_buffer *cb, + struct wlr_color_transform *color_transform); bool vulkan_sync_render_pass_release(struct wlr_vk_renderer *renderer, struct wlr_vk_render_pass *pass); @@ -583,7 +589,7 @@ struct wlr_vk_color_transform { } lut_3d; float color_matrix[9]; - enum wlr_color_transfer_function inverse_eotf; + enum wlr_color_transfer_function eotf; }; void vk_color_transform_destroy(struct wlr_addon *addon); diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index e377c4214..ac9270939 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -258,7 +258,12 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { need_lut = transform->lut_3d.dim > 0; dim = need_lut ? transform->lut_3d.dim : 1; memcpy(matrix, transform->color_matrix, sizeof(matrix)); - tf = transform->inverse_eotf; + tf = transform->eotf; + } + if (need_lut) { + if (!vulkan_command_buffer_ref_color_transform(render_cb, pass->color_transform)) { + goto error; + } } if (pass->color_transform == NULL || need_lut) { wlr_matrix_identity(matrix); @@ -304,7 +309,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { if (need_lut) { lut_ds = transform->lut_3d.ds; } else { - lut_ds = renderer->output_ds_lut3d_dummy; + lut_ds = renderer->ds_lut3d_dummy; } VkDescriptorSet ds[] = { render_buffer->two_pass.blend_descriptor_set, // set 0 @@ -772,6 +777,9 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, pixman_region32_fini(&clip); } +static struct wlr_vk_color_transform *vk_color_transform_create( + struct wlr_vk_renderer *renderer, struct wlr_color_transform *transform, bool output); + static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, const struct wlr_render_texture_options *options) { struct wlr_vk_render_pass *pass = get_render_pass(wlr_pass); @@ -817,37 +825,64 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, }; encode_proj_matrix(matrix, vert_pcr_data.mat4); - enum wlr_color_transfer_function tf; + enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; float color_matrix[9]; - if (!unwrap_texture_color_transform(options->color_transform, color_matrix, &tf)) - { - tf = WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; + bool need_lut = false; + size_t dim = 1; + struct wlr_vk_color_transform *transform = NULL; + if (options->color_transform != NULL) { + transform = get_color_transform(options->color_transform, renderer); + if (transform == NULL) { + transform = vk_color_transform_create(renderer, options->color_transform, false); + if (transform == NULL) { + wlr_log(WLR_ERROR, "Failed to create color transform"); + pass->failed = true; + return; + } + } + need_lut = transform->lut_3d.dim > 0; + dim = need_lut ? transform->lut_3d.dim : 1; + memcpy(color_matrix, transform->color_matrix, sizeof(color_matrix)); + tf = transform->eotf; + } + if (need_lut) { + if (!vulkan_command_buffer_ref_color_transform(pass->command_buffer, + options->color_transform)) { + pass->failed = true; + return; + } + } + if (options->color_transform == NULL || need_lut) { wlr_matrix_identity(color_matrix); } bool srgb_image_view = false; enum wlr_vk_texture_transform tex_transform = 0; - switch (tf) { - case WLR_COLOR_TRANSFER_FUNCTION_SRGB: - if (texture->using_mutable_srgb) { + if (need_lut) { + tex_transform = WLR_VK_TEXTURE_TRANSFORM_LUT_3D; + } else { + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + if (texture->using_mutable_srgb) { + tex_transform = WLR_VK_TEXTURE_TRANSFORM_IDENTITY; + srgb_image_view = true; + } else { + tex_transform = WLR_VK_TEXTURE_TRANSFORM_SRGB; + } + break; + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: tex_transform = WLR_VK_TEXTURE_TRANSFORM_IDENTITY; - srgb_image_view = true; - } else { - tex_transform = WLR_VK_TEXTURE_TRANSFORM_SRGB; + break; + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + tex_transform = WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ; + break; + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: + tex_transform = WLR_VK_TEXTURE_TRANSFORM_GAMMA22; + break; + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + tex_transform = WLR_VK_TEXTURE_TRANSFORM_BT1886; + break; } - break; - case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: - tex_transform = WLR_VK_TEXTURE_TRANSFORM_IDENTITY; - break; - case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: - tex_transform = WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ; - break; - case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: - tex_transform = WLR_VK_TEXTURE_TRANSFORM_GAMMA22; - break; - case WLR_COLOR_TRANSFER_FUNCTION_BT1886: - tex_transform = WLR_VK_TEXTURE_TRANSFORM_BT1886; - break; } enum wlr_color_encoding color_encoding = options->color_encoding; @@ -891,13 +926,26 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, struct wlr_vk_frag_texture_pcr_data frag_pcr_data = { .alpha = alpha, + .lut_3d_offset = 0.5f / dim, + .lut_3d_scale = (float)(dim - 1) / dim, }; encode_color_matrix(color_matrix, frag_pcr_data.matrix); bind_pipeline(pass, pipe->vk); + VkDescriptorSet lut_ds; + if (need_lut) { + lut_ds = transform->lut_3d.ds; + } else { + lut_ds = renderer->ds_lut3d_dummy; + } + VkDescriptorSet ds[] = { + view->ds, // set 0 + lut_ds, // set 1 + }; + size_t ds_len = sizeof(ds) / sizeof(ds[0]); vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, - pipe->layout->vk, 0, 1, &view->ds, 0, NULL); + pipe->layout->vk, 0, ds_len, ds, 0, NULL); vkCmdPushConstants(cb, pipe->layout->vk, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(vert_pcr_data), &vert_pcr_data); @@ -1123,7 +1171,7 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_SHADER_READ_BIT); *ds_pool = vulkan_alloc_texture_ds(renderer, - renderer->output_ds_lut3d_layout, ds); + renderer->ds_lut3d_layout, ds); if (!*ds_pool) { wlr_log(WLR_ERROR, "Failed to allocate descriptor"); goto fail_imageview; @@ -1154,15 +1202,22 @@ fail_image: } static struct wlr_vk_color_transform *vk_color_transform_create( - struct wlr_vk_renderer *renderer, struct wlr_color_transform *transform) { + struct wlr_vk_renderer *renderer, struct wlr_color_transform *transform, + bool output) { struct wlr_vk_color_transform *vk_transform = calloc(1, sizeof(*vk_transform)); if (!vk_transform) { return NULL; } - bool need_lut = !unwrap_output_color_transform(transform, vk_transform->color_matrix, - &vk_transform->inverse_eotf); + bool need_lut; + if (output) { + need_lut = !unwrap_output_color_transform(transform, vk_transform->color_matrix, + &vk_transform->eotf); + } else { + need_lut = !unwrap_texture_color_transform(transform, vk_transform->color_matrix, + &vk_transform->eotf); + } if (need_lut) { vk_transform->lut_3d.dim = 33; @@ -1226,7 +1281,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend if (options != NULL && options->color_transform != NULL && !get_color_transform(options->color_transform, renderer)) { /* Try to create a new color transform */ - if (!vk_color_transform_create(renderer, options->color_transform)) { + if (!vk_color_transform_create(renderer, options->color_transform, true)) { wlr_log(WLR_ERROR, "Failed to create color transform"); return NULL; } diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 434ab4769..8904219ec 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -551,10 +551,12 @@ static void release_command_buffer_resources(struct wlr_vk_command_buffer *cb, wl_list_insert(&renderer->stage.buffers, &buf->link); } - if (cb->color_transform) { - wlr_color_transform_unref(cb->color_transform); - cb->color_transform = NULL; + struct wlr_color_transform **transform; + wl_array_for_each(transform, &cb->color_transforms) { + wlr_color_transform_unref(*transform); } + wl_array_release(&cb->color_transforms); + wl_array_init(&cb->color_transforms); } static struct wlr_vk_command_buffer *get_command_buffer( @@ -666,6 +668,16 @@ void vulkan_reset_command_buffer(struct wlr_vk_command_buffer *cb) { } } +bool vulkan_command_buffer_ref_color_transform(struct wlr_vk_command_buffer *cb, + struct wlr_color_transform *color_transform) { + struct wlr_color_transform **ref = wl_array_add(&cb->color_transforms, sizeof(*ref)); + if (ref == NULL) { + return false; + } + *ref = wlr_color_transform_ref(color_transform); + return true; +} + static void finish_render_buffer_out(struct wlr_vk_render_buffer_out *out, VkDevice dev) { vkDestroyFramebuffer(dev, out->framebuffer, NULL); @@ -1259,9 +1271,9 @@ static void vulkan_destroy(struct wlr_renderer *wlr_renderer) { 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); + vkDestroyDescriptorSetLayout(dev->dev, renderer->ds_lut3d_layout, NULL); vkDestroyCommandPool(dev->dev, renderer->command_pool, NULL); - vkDestroySampler(dev->dev, renderer->output_sampler_lut3d, NULL); + vkDestroySampler(dev->dev, renderer->sampler_lut3d, NULL); if (renderer->read_pixels_cache.initialized) { vkFreeMemory(dev->dev, renderer->read_pixels_cache.dst_img_memory, NULL); @@ -1680,10 +1692,15 @@ static bool init_tex_layouts(struct wlr_vk_renderer *renderer, }, }; + VkDescriptorSetLayout out_ds_layouts[] = { + *out_ds_layout, + renderer->ds_lut3d_layout, + }; + VkPipelineLayoutCreateInfo pl_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, - .setLayoutCount = 1, - .pSetLayouts = out_ds_layout, + .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, }; @@ -1734,7 +1751,7 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) { }; res = vkCreateSampler(renderer->dev->dev, &sampler_create_info, NULL, - &renderer->output_sampler_lut3d); + &renderer->sampler_lut3d); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateSampler", res); return false; @@ -1745,7 +1762,7 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) { .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, - .pImmutableSamplers = &renderer->output_sampler_lut3d, + .pImmutableSamplers = &renderer->sampler_lut3d, }; VkDescriptorSetLayoutCreateInfo ds_lut3d_info = { @@ -1755,7 +1772,7 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) { }; res = vkCreateDescriptorSetLayout(dev, &ds_lut3d_info, NULL, - &renderer->output_ds_lut3d_layout); + &renderer->ds_lut3d_layout); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateDescriptorSetLayout", res); return false; @@ -1777,7 +1794,7 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) { VkDescriptorSetLayout out_ds_layouts[] = { renderer->output_ds_srgb_layout, - renderer->output_ds_lut3d_layout, + renderer->ds_lut3d_layout, }; VkPipelineLayoutCreateInfo pl_info = { @@ -2307,9 +2324,9 @@ static bool init_dummy_images(struct wlr_vk_renderer *renderer) { 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) { + renderer->ds_lut3d_dummy_pool = vulkan_alloc_texture_ds(renderer, + renderer->ds_lut3d_layout, &renderer->ds_lut3d_dummy); + if (!renderer->ds_lut3d_dummy_pool) { wlr_log(WLR_ERROR, "Failed to allocate descriptor"); return false; } @@ -2321,7 +2338,7 @@ static bool init_dummy_images(struct wlr_vk_renderer *renderer) { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .dstSet = renderer->output_ds_lut3d_dummy, + .dstSet = renderer->ds_lut3d_dummy, .pImageInfo = &ds_img_info, }; vkUpdateDescriptorSets(dev, 1, &ds_write, 0, NULL); diff --git a/render/vulkan/shaders/texture.frag b/render/vulkan/shaders/texture.frag index 03c24eceb..7743352cf 100644 --- a/render/vulkan/shaders/texture.frag +++ b/render/vulkan/shaders/texture.frag @@ -2,6 +2,8 @@ layout(set = 0, binding = 0) uniform sampler2D tex; +layout(set = 1, binding = 0) uniform sampler3D lut_3d; + layout(location = 0) in vec2 uv; layout(location = 0) out vec4 out_color; @@ -9,6 +11,8 @@ layout(location = 0) out vec4 out_color; layout(push_constant, row_major) uniform UBO { layout(offset = 80) mat4 matrix; float alpha; + float lut_3d_offset; + float lut_3d_scale; } data; layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0; @@ -19,6 +23,7 @@ layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0; #define TEXTURE_TRANSFORM_ST2084_PQ 2 #define TEXTURE_TRANSFORM_GAMMA22 3 #define TEXTURE_TRANSFORM_BT1886 4 +#define TEXTURE_TRANSFORM_LUT_3D 5 float srgb_channel_to_linear(float x) { return mix(x / 12.92, @@ -79,6 +84,10 @@ void main() { rgb = pow(rgb, vec3(2.2)); } else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_BT1886) { rgb = bt1886_color_to_linear(rgb); + } else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_LUT_3D) { + // Apply 3D LUT + vec3 pos = data.lut_3d_offset + rgb * data.lut_3d_scale; + rgb = texture(lut_3d, pos).rgb; } rgb = mat3(data.matrix) * rgb;