vulkan/wsi: implement the Wayland color management protocol

This allows applications to use color spaces other than sRGB, if the compositor
supports them.

The color management surface is only created if a non-sRGB and non-passthrough
colorspace is set on the surface, so applications can still use the protocol
directly if they prefer.

Co-authored-by: Xaver Hugl <xaver.hugl@kde.org>
Reviewed-by: Sebastian Wick <sebastian.wick@redhat.com>
Acked-by: Daniel Stone <daniels@collabora.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/32038>
This commit is contained in:
Colin Marc 2024-03-26 11:12:28 +01:00 committed by Marge Bot
parent 53b40a40f4
commit 789507c99c
3 changed files with 400 additions and 23 deletions

View file

@ -48,6 +48,7 @@ wp_protos = {
'presentation-time': 'stable/presentation-time/presentation-time.xml',
'tearing-control-v1': 'staging/tearing-control/tearing-control-v1.xml',
'linux-drm-syncobj-v1': 'staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml',
'color-management-v1': 'staging/color-management/color-management-v1.xml',
}
wp_files = {}
foreach name, xml : wp_protos

View file

@ -22,6 +22,7 @@ if with_platform_wayland
files_vulkan_wsi += wp_files['tearing-control-v1']
links_vulkan_wsi += libloader_wayland_helper
files_vulkan_wsi += wp_files['linux-drm-syncobj-v1']
files_vulkan_wsi += wp_files['color-management-v1']
endif
if with_platform_windows

View file

@ -47,6 +47,7 @@
#include "presentation-time-client-protocol.h"
#include "linux-drm-syncobj-v1-client-protocol.h"
#include "tearing-control-v1-client-protocol.h"
#include "color-management-v1-client-protocol.h"
#include <util/cnd_monotonic.h>
#include <util/compiler.h>
@ -110,8 +111,13 @@ struct wsi_wl_display {
struct wp_tearing_control_manager_v1 *tearing_control_manager;
struct wp_linux_drm_syncobj_manager_v1 *wl_syncobj;
struct wp_color_manager_v1 *color_manager;
struct dmabuf_feedback_format_table format_table;
struct u_vector color_primaries;
struct u_vector color_transfer_funcs;
/* users want per-chain wsi_wl_swapchain->present_ids.wp_presentation */
struct wp_presentation *wp_presentation_notwrapped;
uint32_t wp_presentation_version;
@ -125,6 +131,9 @@ struct wsi_wl_display {
/* Formats populated by zwp_linux_dmabuf_v1 or wl_shm interfaces */
struct u_vector formats;
/* Additional colorspaces returned by wp_color_management_v1. */
struct u_vector colorspaces;
bool sw;
dev_t main_device;
@ -180,6 +189,11 @@ struct wsi_wl_surface {
struct dmabuf_feedback dmabuf_feedback, pending_dmabuf_feedback;
struct wp_linux_drm_syncobj_surface_v1 *wl_syncobj_surface;
struct vk_instance *instance;
struct wp_color_management_surface_v1 *color_surface;
int color_surface_refcount;
VkColorSpaceKHR colorspace;
};
struct wsi_wl_swapchain {
@ -230,6 +244,8 @@ struct wsi_wl_swapchain {
unsigned int refresh_nsec;
} present_ids;
VkColorSpaceKHR colorspace;
struct wsi_wl_image images[0];
};
VK_DEFINE_NONDISP_HANDLE_CASTS(wsi_wl_swapchain, base.base, VkSwapchainKHR,
@ -885,6 +901,316 @@ static const struct wl_shm_listener shm_listener = {
.format = shm_handle_format
};
static bool
vector_contains(struct u_vector *vec, unsigned int val)
{
unsigned int *ptr;
u_vector_foreach(ptr, vec)
if (*ptr == val)
return true;
return false;
}
struct Colorspace {
VkColorSpaceKHR colorspace;
enum wp_color_manager_v1_primaries primaries;
enum wp_color_manager_v1_transfer_function tf;
};
struct Colorspace colorspace_mapping[] = {
{
.colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
.primaries = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
.tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB,
},
{
.colorspace = VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT,
.primaries = WP_COLOR_MANAGER_V1_PRIMARIES_DISPLAY_P3,
.tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB,
},
{
.colorspace = VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT,
.primaries = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
.tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR,
},
{
.colorspace = VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT,
.primaries = WP_COLOR_MANAGER_V1_PRIMARIES_DISPLAY_P3,
.tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR,
},
{
.colorspace = VK_COLOR_SPACE_BT709_LINEAR_EXT,
.primaries = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
.tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR,
},
{
.colorspace = VK_COLOR_SPACE_BT709_NONLINEAR_EXT,
.primaries = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
.tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886,
},
{
.colorspace = VK_COLOR_SPACE_BT2020_LINEAR_EXT,
.primaries = WP_COLOR_MANAGER_V1_PRIMARIES_BT2020,
.tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR,
},
{
.colorspace = VK_COLOR_SPACE_HDR10_ST2084_EXT,
.primaries = WP_COLOR_MANAGER_V1_PRIMARIES_BT2020,
.tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ,
},
/* VK_COLOR_SPACE_DOLBYVISION_EXT is left out because it's deprecated */
{
.colorspace = VK_COLOR_SPACE_HDR10_HLG_EXT,
.primaries = WP_COLOR_MANAGER_V1_PRIMARIES_BT2020,
.tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG,
},
{
.colorspace = VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT,
.primaries = WP_COLOR_MANAGER_V1_PRIMARIES_ADOBE_RGB,
.tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR,
},
/* VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT is left out because there's no
* exactly matching transfer function in the Wayland protocol */
/* VK_COLOR_SPACE_PASS_THROUGH_EXT is handled elsewhere */
{
.colorspace = VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT,
.primaries = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
.tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_SRGB,
},
/* VK_COLOR_SPACE_DISPLAY_NATIVE_AMD isn't supported */
/* VK_COLORSPACE_SRGB_NONLINEAR_KHR is just an alias */
/* VK_COLOR_SPACE_DCI_P3_LINEAR_EXT is just an alias */
};
static int
wsi_wl_display_determine_colorspaces(struct wsi_wl_display *display)
{
u_vector_finish(&display->colorspaces);
if (!u_vector_init(&display->colorspaces, 8, sizeof(VkColorSpaceKHR)))
return -1;
/* SRGB_NONLINEAR is always supported. */
VkColorSpaceKHR *new_cs = u_vector_add(&display->colorspaces);
if (!new_cs)
return -1;
*new_cs = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
/* as is PASS_THROUGH */
new_cs = u_vector_add(&display->colorspaces);
if (!new_cs)
return -1;
*new_cs = VK_COLOR_SPACE_PASS_THROUGH_EXT;
if (!display->color_manager)
return 0;
struct u_vector *tfs = &display->color_transfer_funcs;
struct u_vector *primaries = &display->color_primaries;
for (int i = 0; i < ARRAY_SIZE(colorspace_mapping); i++) {
if (!vector_contains(primaries, colorspace_mapping[i].primaries))
continue;
if (!vector_contains(tfs, colorspace_mapping[i].tf))
continue;
VkColorSpaceKHR *new_cs = u_vector_add(&display->colorspaces);
if (!new_cs)
return -1;
*new_cs = colorspace_mapping[i].colorspace;
}
return 0;
}
static void
color_management_handle_supported_intent(void *data,
struct wp_color_manager_v1 *color_manager,
unsigned int intent)
{
/* We only use the perceptual rendering intent, which is always supported. */
}
static void
color_management_handle_supported_features(void *data,
struct wp_color_manager_v1 *color_manager,
unsigned int feature)
{
/* We don't use any non-default features yet. */
}
static void
color_management_handle_supported_tf_named(void *data,
struct wp_color_manager_v1 *color_manager,
unsigned int tf)
{
struct wsi_wl_display *display = data;
unsigned int *new_tf = u_vector_add(&display->color_transfer_funcs);
if (new_tf)
*new_tf = tf;
}
static void
color_management_handle_supported_primaries_named(void *data,
struct wp_color_manager_v1 *color_manager,
unsigned int primaries)
{
struct wsi_wl_display *display = data;
unsigned int *new_primaries = u_vector_add(&display->color_primaries);
if (new_primaries)
*new_primaries = primaries;
}
static void
color_management_handle_done(void *data, struct wp_color_manager_v1 *color_manager)
{
/* Intentionally left blank */
}
static const struct wp_color_manager_v1_listener color_manager_listener = {
.supported_intent = color_management_handle_supported_intent,
.supported_feature = color_management_handle_supported_features,
.supported_tf_named = color_management_handle_supported_tf_named,
.supported_primaries_named = color_management_handle_supported_primaries_named,
.done = color_management_handle_done,
};
enum image_description_status {
undefined,
ready,
failed,
};
static void
color_management_handle_image_desc_failed(void *data,
struct wp_image_description_v1 *desc,
unsigned int cause,
const char *msg)
{
enum image_description_status *status = data;
*status = failed;
}
static void
color_management_handle_image_desc_ready(void *data,
struct wp_image_description_v1 *desc,
unsigned int id)
{
enum image_description_status *status = data;
*status = ready;
}
static const struct wp_image_description_v1_listener image_description_listener = {
.failed = color_management_handle_image_desc_failed,
.ready = color_management_handle_image_desc_ready,
};
static bool
needs_color_surface(VkColorSpaceKHR colorspace)
{
return colorspace != VK_COLOR_SPACE_PASS_THROUGH_EXT;
}
static void
wsi_wl_surface_add_color_refcount(struct wsi_wl_surface *wsi_surface)
{
wsi_surface->color_surface_refcount++;
if (wsi_surface->color_surface_refcount == 1) {
wsi_surface->color_surface =
wp_color_manager_v1_get_surface(wsi_surface->display->color_manager, wsi_surface->surface);
}
}
static void
wsi_wl_surface_remove_color_refcount(struct wsi_wl_surface *wsi_surface)
{
wsi_surface->color_surface_refcount--;
if (wsi_surface->color_surface_refcount == 0) {
wp_color_management_surface_v1_destroy(wsi_surface->color_surface);
wsi_surface->color_surface = NULL;
}
}
static VkResult
wsi_wl_swapchain_update_colorspace(struct wsi_wl_swapchain *chain)
{
struct wsi_wl_surface *surface = chain->wsi_wl_surface;
struct wsi_wl_display *display = surface->display;
/* we need the color management extension for
* everything except sRGB and PASS_THROUGH */
if (!display->color_manager) {
if (chain->colorspace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR ||
chain->colorspace == VK_COLOR_SPACE_PASS_THROUGH_EXT) {
return VK_SUCCESS;
} else {
return VK_ERROR_SURFACE_LOST_KHR;
}
}
bool new_color_surface = !surface->color_surface;
bool needs_color_surface_new = needs_color_surface(chain->colorspace);
bool needs_color_surface_old = needs_color_surface(surface->colorspace);
if ((new_color_surface || !needs_color_surface_old) && needs_color_surface_new) {
wsi_wl_surface_add_color_refcount(surface);
} else if (needs_color_surface_old && !needs_color_surface_new) {
wsi_wl_surface_remove_color_refcount(surface);
}
if (!new_color_surface && surface->colorspace == chain->colorspace)
return VK_SUCCESS;
/* failure is fatal, so this potentially being wrong
in that case doesn't matter */
surface->colorspace = chain->colorspace;
if (!needs_color_surface_new)
return VK_SUCCESS;
struct wp_image_description_creator_params_v1 *creator =
wp_color_manager_v1_create_parametric_creator(display->color_manager);
if (!creator)
return VK_ERROR_SURFACE_LOST_KHR;
unsigned int primaries = 0;
unsigned int tf = 0;
for (int i = 0; i < ARRAY_SIZE(colorspace_mapping); i++) {
if (colorspace_mapping[i].colorspace == chain->colorspace) {
primaries = colorspace_mapping[i].primaries;
tf = colorspace_mapping[i].tf;
}
}
if (!primaries)
return VK_ERROR_SURFACE_LOST_KHR;
wp_image_description_creator_params_v1_set_primaries_named(creator, primaries);
wp_image_description_creator_params_v1_set_tf_named(creator, tf);
wl_proxy_set_queue((struct wl_proxy *) creator, display->queue);
struct wp_image_description_v1 *image_desc =
wp_image_description_creator_params_v1_create(creator);
if (!image_desc)
return VK_ERROR_SURFACE_LOST_KHR;
enum image_description_status status = undefined;
wp_image_description_v1_add_listener(image_desc, &image_description_listener, &status);
while (status == undefined) {
int ret = wl_display_dispatch_queue(display->wl_display, display->queue);
if (ret < 0)
return VK_ERROR_OUT_OF_DATE_KHR;
}
if (status == failed)
return VK_ERROR_SURFACE_LOST_KHR;
wp_color_management_surface_v1_set_image_description(chain->wsi_wl_surface->color_surface,
image_desc,
WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL);
wp_image_description_v1_destroy(image_desc);
return VK_SUCCESS;
}
static void
presentation_handle_clock_id(void* data, struct wp_presentation *wp_presentation, uint32_t clk_id)
{
@ -942,6 +1268,17 @@ registry_handle_global(void *data, struct wl_registry *registry,
display->commit_timing_manager =
wl_registry_bind(registry, name, &wp_commit_timing_manager_v1_interface, 1);
}
if (strcmp(interface, wp_color_manager_v1_interface.name) == 0) {
display->color_manager =
wl_registry_bind(registry, name, &wp_color_manager_v1_interface, 1);
u_vector_init(&display->color_primaries, 8, sizeof(uint32_t));
u_vector_init(&display->color_transfer_funcs, 8, sizeof(uint32_t));
wp_color_manager_v1_add_listener(display->color_manager,
&color_manager_listener, display);
}
}
static void
@ -961,6 +1298,10 @@ wsi_wl_display_finish(struct wsi_wl_display *display)
u_vector_foreach(f, &display->formats)
u_vector_finish(&f->modifiers);
u_vector_finish(&display->formats);
u_vector_finish(&display->colorspaces);
u_vector_finish(&display->color_primaries);
u_vector_finish(&display->color_transfer_funcs);
if (display->wl_shm)
wl_shm_destroy(display->wl_shm);
if (display->wl_syncobj)
@ -975,6 +1316,8 @@ wsi_wl_display_finish(struct wsi_wl_display *display)
wp_commit_timing_manager_v1_destroy(display->commit_timing_manager);
if (display->tearing_control_manager)
wp_tearing_control_manager_v1_destroy(display->tearing_control_manager);
if (display->color_manager)
wp_color_manager_v1_destroy(display->color_manager);
if (display->wl_display_wrapper)
wl_proxy_wrapper_destroy(display->wl_display_wrapper);
if (display->queue)
@ -1066,9 +1409,14 @@ wsi_wl_display_init(struct wsi_wayland *wsi_wl,
}
}
/* Round-trip again to get formats and modifiers */
/* Round-trip again to get formats, modifiers and color properties */
wl_display_roundtrip_queue(display->wl_display, display->queue);
if (wsi_wl_display_determine_colorspaces(display) < 0) {
result = VK_ERROR_OUT_OF_HOST_MEMORY;
goto fail;
}
if (wsi_wl->wsi->force_bgra8_unorm_first) {
/* Find BGRA8_UNORM in the list and swap it to the first position if we
* can find it. Some apps get confused if SRGB is first in the list.
@ -1359,6 +1707,8 @@ wsi_wl_surface_get_formats(VkIcdSurfaceBase *icd_surface,
VkSurfaceFormatKHR* pSurfaceFormats)
{
VkIcdSurfaceWayland *surface = (VkIcdSurfaceWayland *)icd_surface;
struct wsi_wl_surface *wsi_wl_surface =
wl_container_of((VkIcdSurfaceWayland *)icd_surface, wsi_wl_surface, base);
struct wsi_wayland *wsi =
(struct wsi_wayland *)wsi_device->wsi[VK_ICD_WSI_PLATFORM_WAYLAND];
@ -1370,18 +1720,22 @@ wsi_wl_surface_get_formats(VkIcdSurfaceBase *icd_surface,
VK_OUTARRAY_MAKE_TYPED(VkSurfaceFormatKHR, out,
pSurfaceFormats, pSurfaceFormatCount);
struct wsi_wl_format *disp_fmt;
u_vector_foreach(disp_fmt, &display.formats) {
/* Skip formats for which we can't support both alpha & opaque
* formats.
*/
if (!(disp_fmt->flags & WSI_WL_FMT_ALPHA) ||
!(disp_fmt->flags & WSI_WL_FMT_OPAQUE))
continue;
VkColorSpaceKHR *cs;
u_vector_foreach(cs, &display.colorspaces) {
struct wsi_wl_format *disp_fmt;
u_vector_foreach(disp_fmt, &display.formats) {
/* Skip formats for which we can't support both alpha & opaque
* formats.
*/
if (!(disp_fmt->flags & WSI_WL_FMT_ALPHA) ||
!(disp_fmt->flags & WSI_WL_FMT_OPAQUE)) {
continue;
}
vk_outarray_append_typed(VkSurfaceFormatKHR, &out, out_fmt) {
out_fmt->format = disp_fmt->vk_format;
out_fmt->colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
vk_outarray_append_typed(VkSurfaceFormatKHR, &out, out_fmt) {
out_fmt->format = disp_fmt->vk_format;
out_fmt->colorSpace = *cs;
}
}
}
@ -1398,6 +1752,8 @@ wsi_wl_surface_get_formats2(VkIcdSurfaceBase *icd_surface,
VkSurfaceFormat2KHR* pSurfaceFormats)
{
VkIcdSurfaceWayland *surface = (VkIcdSurfaceWayland *)icd_surface;
struct wsi_wl_surface *wsi_wl_surface =
wl_container_of((VkIcdSurfaceWayland *)icd_surface, wsi_wl_surface, base);
struct wsi_wayland *wsi =
(struct wsi_wayland *)wsi_device->wsi[VK_ICD_WSI_PLATFORM_WAYLAND];
@ -1409,18 +1765,22 @@ wsi_wl_surface_get_formats2(VkIcdSurfaceBase *icd_surface,
VK_OUTARRAY_MAKE_TYPED(VkSurfaceFormat2KHR, out,
pSurfaceFormats, pSurfaceFormatCount);
struct wsi_wl_format *disp_fmt;
u_vector_foreach(disp_fmt, &display.formats) {
/* Skip formats for which we can't support both alpha & opaque
* formats.
*/
if (!(disp_fmt->flags & WSI_WL_FMT_ALPHA) ||
!(disp_fmt->flags & WSI_WL_FMT_OPAQUE))
continue;
VkColorSpaceKHR *cs;
u_vector_foreach(cs, &display.colorspaces) {
struct wsi_wl_format *disp_fmt;
u_vector_foreach(disp_fmt, &display.formats) {
/* Skip formats for which we can't support both alpha & opaque
* formats.
*/
if (!(disp_fmt->flags & WSI_WL_FMT_ALPHA) ||
!(disp_fmt->flags & WSI_WL_FMT_OPAQUE)) {
continue;
}
vk_outarray_append_typed(VkSurfaceFormat2KHR, &out, out_fmt) {
out_fmt->surfaceFormat.format = disp_fmt->vk_format;
out_fmt->surfaceFormat.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
vk_outarray_append_typed(VkSurfaceFormat2KHR, &out, out_fmt) {
out_fmt->surfaceFormat.format = disp_fmt->vk_format;
out_fmt->surfaceFormat.colorSpace = *cs;
}
}
}
@ -1516,6 +1876,9 @@ wsi_wl_surface_destroy(VkIcdSurfaceBase *icd_surface, VkInstance _instance,
dmabuf_feedback_fini(&wsi_wl_surface->pending_dmabuf_feedback);
}
if (wsi_wl_surface->color_surface)
wp_color_management_surface_v1_destroy(wsi_wl_surface->color_surface);
if (wsi_wl_surface->surface)
wl_proxy_wrapper_destroy(wsi_wl_surface->surface);
@ -1838,6 +2201,9 @@ wsi_CreateWaylandSurfaceKHR(VkInstance _instance,
surface->display = pCreateInfo->display;
surface->surface = pCreateInfo->surface;
wsi_wl_surface->instance = instance;
wsi_wl_surface->colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
*pSurface = VkIcdSurfaceBase_to_handle(&surface->base);
return VK_SUCCESS;
@ -2460,6 +2826,10 @@ wsi_wl_swapchain_queue_present(struct wsi_swapchain *wsi_chain,
image->base.row_pitches[0] * chain->extent.height);
}
VkResult ret = wsi_wl_swapchain_update_colorspace(chain);
if (ret != VK_SUCCESS)
return ret;
/* For EXT_swapchain_maintenance1. We might have transitioned from FIFO to MAILBOX.
* In this case we need to let the FIFO request complete, before presenting MAILBOX. */
while (!chain->legacy_fifo_ready) {
@ -2816,6 +3186,9 @@ wsi_wl_swapchain_chain_free(struct wsi_wl_swapchain *chain,
wl_callback_destroy(chain->frame);
if (chain->tearing_control)
wp_tearing_control_v1_destroy(chain->tearing_control);
if (needs_color_surface(chain->colorspace) && wsi_wl_surface->color_surface) {
wsi_wl_surface_remove_color_refcount(wsi_wl_surface);
}
/* Only unregister if we are the non-retired swapchain, or
* we are a retired swapchain and memory allocation failed,
@ -2980,6 +3353,8 @@ wsi_wl_surface_create_swapchain(VkIcdSurfaceBase *icd_surface,
WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC);
}
chain->colorspace = pCreateInfo->imageColorSpace;
enum wsi_wl_buffer_type buffer_type;
struct wsi_base_image_params *image_params = NULL;
struct wsi_cpu_image_params cpu_image_params;