| /* |
| * Copyright © 2014, 2015 Collabora, Ltd. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files (the |
| * "Software"), to deal in the Software without restriction, including |
| * without limitation the rights to use, copy, modify, merge, publish, |
| * distribute, sublicense, and/or sell copies of the Software, and to |
| * permit persons to whom the Software is furnished to do so, subject to |
| * the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the |
| * next paragraph) shall be included in all copies or substantial |
| * portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #include "config.h" |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <sys/types.h> |
| |
| #include <libweston/libweston.h> |
| #include "linux-dmabuf.h" |
| #include "shared/os-compatibility.h" |
| #include "libweston-internal.h" |
| #include "shared/weston-drm-fourcc.h" |
| |
| static void |
| linux_dmabuf_buffer_destroy(struct linux_dmabuf_buffer *buffer) |
| { |
| int i; |
| |
| for (i = 0; i < buffer->attributes.n_planes; i++) { |
| close(buffer->attributes.fd[i]); |
| buffer->attributes.fd[i] = -1; |
| } |
| |
| buffer->attributes.n_planes = 0; |
| free(buffer); |
| } |
| |
| static void |
| destroy_params(struct wl_resource *params_resource) |
| { |
| struct linux_dmabuf_buffer *buffer; |
| |
| buffer = wl_resource_get_user_data(params_resource); |
| |
| if (!buffer) |
| return; |
| |
| linux_dmabuf_buffer_destroy(buffer); |
| } |
| |
| static void |
| params_destroy(struct wl_client *client, struct wl_resource *resource) |
| { |
| wl_resource_destroy(resource); |
| } |
| |
| static void |
| params_add(struct wl_client *client, |
| struct wl_resource *params_resource, |
| int32_t name_fd, |
| uint32_t plane_idx, |
| uint32_t offset, |
| uint32_t stride, |
| uint32_t modifier_hi, |
| uint32_t modifier_lo) |
| { |
| struct linux_dmabuf_buffer *buffer; |
| |
| buffer = wl_resource_get_user_data(params_resource); |
| if (!buffer) { |
| wl_resource_post_error(params_resource, |
| ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, |
| "params was already used to create a wl_buffer"); |
| close(name_fd); |
| return; |
| } |
| |
| assert(buffer->params_resource == params_resource); |
| assert(!buffer->buffer_resource); |
| |
| if (plane_idx >= MAX_DMABUF_PLANES) { |
| wl_resource_post_error(params_resource, |
| ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX, |
| "plane index %u is too high", plane_idx); |
| close(name_fd); |
| return; |
| } |
| |
| if (buffer->attributes.fd[plane_idx] != -1) { |
| wl_resource_post_error(params_resource, |
| ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET, |
| "a dmabuf has already been added for plane %u", |
| plane_idx); |
| close(name_fd); |
| return; |
| } |
| |
| buffer->attributes.fd[plane_idx] = name_fd; |
| buffer->attributes.offset[plane_idx] = offset; |
| buffer->attributes.stride[plane_idx] = stride; |
| |
| if (wl_resource_get_version(params_resource) < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) |
| buffer->attributes.modifier[plane_idx] = DRM_FORMAT_MOD_LINEAR; |
| else |
| buffer->attributes.modifier[plane_idx] = ((uint64_t)modifier_hi << 32) | |
| modifier_lo; |
| |
| buffer->attributes.n_planes++; |
| } |
| |
| static void |
| linux_dmabuf_wl_buffer_destroy(struct wl_client *client, |
| struct wl_resource *resource) |
| { |
| wl_resource_destroy(resource); |
| } |
| |
| static const struct wl_buffer_interface linux_dmabuf_buffer_implementation = { |
| linux_dmabuf_wl_buffer_destroy |
| }; |
| |
| static void |
| destroy_linux_dmabuf_wl_buffer(struct wl_resource *resource) |
| { |
| struct linux_dmabuf_buffer *buffer; |
| |
| buffer = wl_resource_get_user_data(resource); |
| assert(buffer->buffer_resource == resource); |
| assert(!buffer->params_resource); |
| |
| if (buffer->user_data_destroy_func) |
| buffer->user_data_destroy_func(buffer); |
| |
| linux_dmabuf_buffer_destroy(buffer); |
| } |
| |
| static void |
| params_create_common(struct wl_client *client, |
| struct wl_resource *params_resource, |
| uint32_t buffer_id, |
| int32_t width, |
| int32_t height, |
| uint32_t format, |
| uint32_t flags) |
| { |
| struct linux_dmabuf_buffer *buffer; |
| int i; |
| |
| buffer = wl_resource_get_user_data(params_resource); |
| |
| if (!buffer) { |
| wl_resource_post_error(params_resource, |
| ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, |
| "params was already used to create a wl_buffer"); |
| return; |
| } |
| |
| assert(buffer->params_resource == params_resource); |
| assert(!buffer->buffer_resource); |
| |
| /* Switch the linux_dmabuf_buffer object from params resource to |
| * eventually wl_buffer resource. |
| */ |
| wl_resource_set_user_data(buffer->params_resource, NULL); |
| buffer->params_resource = NULL; |
| |
| if (!buffer->attributes.n_planes) { |
| wl_resource_post_error(params_resource, |
| ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, |
| "no dmabuf has been added to the params"); |
| goto err_out; |
| } |
| |
| /* Check for holes in the dmabufs set (e.g. [0, 1, 3]) */ |
| for (i = 0; i < buffer->attributes.n_planes; i++) { |
| if (buffer->attributes.fd[i] == -1) { |
| wl_resource_post_error(params_resource, |
| ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, |
| "no dmabuf has been added for plane %i", i); |
| goto err_out; |
| } |
| } |
| |
| buffer->attributes.width = width; |
| buffer->attributes.height = height; |
| buffer->attributes.format = format; |
| buffer->attributes.flags = flags; |
| |
| WESTON_LOGD(COMMON_LOG, "dmabuf:%p fd(%d %d) wxh(%dx%d) format:%u", buffer, |
| buffer->attributes.fd[0], buffer->attributes.fd[1], width, height, format); |
| |
| if (width < 1 || height < 1) { |
| wl_resource_post_error(params_resource, |
| ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS, |
| "invalid width %d or height %d", width, height); |
| goto err_out; |
| } |
| |
| for (i = 0; i < buffer->attributes.n_planes; i++) { |
| off_t size; |
| |
| if ((uint64_t) buffer->attributes.offset[i] + buffer->attributes.stride[i] > UINT32_MAX) { |
| wl_resource_post_error(params_resource, |
| ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, |
| "size overflow for plane %i", i); |
| goto err_out; |
| } |
| |
| if (i == 0 && |
| (uint64_t) buffer->attributes.offset[i] + |
| (uint64_t) buffer->attributes.stride[i] * height > UINT32_MAX) { |
| wl_resource_post_error(params_resource, |
| ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, |
| "size overflow for plane %i", i); |
| goto err_out; |
| } |
| |
| /* Don't report an error as it might be caused |
| * by the kernel not supporting seeking on dmabuf */ |
| if (buffer->direct_display) { |
| weston_log("direct_display. remove parameter check and gpu import \n"); |
| goto avoid_gpu_import; |
| } |
| |
| size = lseek(buffer->attributes.fd[i], 0, SEEK_END); |
| if (size == -1) |
| continue; |
| |
| if (buffer->attributes.offset[i] >= size) { |
| wl_resource_post_error(params_resource, |
| ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, |
| "invalid offset %i for plane %i", |
| buffer->attributes.offset[i], i); |
| goto err_out; |
| } |
| |
| if (buffer->attributes.offset[i] + buffer->attributes.stride[i] > size) { |
| wl_resource_post_error(params_resource, |
| ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, |
| "invalid stride %i for plane %i", |
| buffer->attributes.stride[i], i); |
| goto err_out; |
| } |
| weston_log("\n %s %d offset:%d, stride:%d height:%d,size:%d\n", __FUNCTION__, __LINE__, |
| buffer->attributes.offset[i], buffer->attributes.stride[i], height, size); |
| /* Only valid for first plane as other planes might be |
| * sub-sampled according to fourcc format */ |
| if (i == 0 && |
| buffer->attributes.offset[i] + buffer->attributes.stride[i] * height > size) { |
| wl_resource_post_error(params_resource, |
| ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, |
| "invalid buffer stride or height for plane %i", i); |
| goto err_out; |
| } |
| } |
| |
| if (buffer->direct_display) { |
| if (!weston_compositor_dmabuf_can_scanout(buffer->compositor, |
| buffer)) |
| goto err_failed; |
| |
| goto avoid_gpu_import; |
| } |
| |
| if (!weston_compositor_import_dmabuf(buffer->compositor, buffer)) |
| goto err_failed; |
| |
| avoid_gpu_import: |
| buffer->buffer_resource = wl_resource_create(client, |
| &wl_buffer_interface, |
| 1, buffer_id); |
| if (!buffer->buffer_resource) { |
| wl_resource_post_no_memory(params_resource); |
| goto err_buffer; |
| } |
| |
| wl_resource_set_implementation(buffer->buffer_resource, |
| &linux_dmabuf_buffer_implementation, |
| buffer, destroy_linux_dmabuf_wl_buffer); |
| |
| /* send 'created' event when the request is not for an immediate |
| * import, ie buffer_id is zero */ |
| if (buffer_id == 0) |
| zwp_linux_buffer_params_v1_send_created(params_resource, |
| buffer->buffer_resource); |
| |
| return; |
| |
| err_buffer: |
| if (buffer->user_data_destroy_func) |
| buffer->user_data_destroy_func(buffer); |
| |
| err_failed: |
| if (buffer_id == 0) |
| zwp_linux_buffer_params_v1_send_failed(params_resource); |
| else |
| /* since the behavior is left implementation defined by the |
| * protocol in case of create_immed failure due to an unknown cause, |
| * we choose to treat it as a fatal error and immediately kill the |
| * client instead of creating an invalid handle and waiting for it |
| * to be used. |
| */ |
| wl_resource_post_error(params_resource, |
| ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER, |
| "importing the supplied dmabufs failed"); |
| |
| err_out: |
| linux_dmabuf_buffer_destroy(buffer); |
| } |
| |
| static void |
| params_create(struct wl_client *client, |
| struct wl_resource *params_resource, |
| int32_t width, |
| int32_t height, |
| uint32_t format, |
| uint32_t flags) |
| { |
| params_create_common(client, params_resource, 0, width, height, format, |
| flags); |
| } |
| |
| static void |
| params_create_immed(struct wl_client *client, |
| struct wl_resource *params_resource, |
| uint32_t buffer_id, |
| int32_t width, |
| int32_t height, |
| uint32_t format, |
| uint32_t flags) |
| { |
| params_create_common(client, params_resource, buffer_id, width, height, |
| format, flags); |
| } |
| |
| static const struct zwp_linux_buffer_params_v1_interface |
| zwp_linux_buffer_params_implementation = { |
| params_destroy, |
| params_add, |
| params_create, |
| params_create_immed |
| }; |
| |
| static void |
| linux_dmabuf_destroy(struct wl_client *client, struct wl_resource *resource) |
| { |
| wl_resource_destroy(resource); |
| } |
| |
| static void |
| linux_dmabuf_create_params(struct wl_client *client, |
| struct wl_resource *linux_dmabuf_resource, |
| uint32_t params_id) |
| { |
| struct weston_compositor *compositor; |
| struct linux_dmabuf_buffer *buffer; |
| uint32_t version; |
| int i; |
| |
| version = wl_resource_get_version(linux_dmabuf_resource); |
| compositor = wl_resource_get_user_data(linux_dmabuf_resource); |
| |
| buffer = zalloc(sizeof *buffer); |
| if (!buffer) |
| goto err_out; |
| |
| for (i = 0; i < MAX_DMABUF_PLANES; i++) |
| buffer->attributes.fd[i] = -1; |
| |
| buffer->compositor = compositor; |
| buffer->params_resource = |
| wl_resource_create(client, |
| &zwp_linux_buffer_params_v1_interface, |
| version, params_id); |
| buffer->direct_display = false; |
| if (!buffer->params_resource) |
| goto err_dealloc; |
| |
| wl_resource_set_implementation(buffer->params_resource, |
| &zwp_linux_buffer_params_implementation, |
| buffer, destroy_params); |
| |
| return; |
| |
| err_dealloc: |
| free(buffer); |
| |
| err_out: |
| wl_resource_post_no_memory(linux_dmabuf_resource); |
| } |
| |
| /** Creates dma-buf feedback tranche |
| * |
| * The tranche is added to dma-buf feedback's tranche list |
| * |
| * @param dmabuf_feedback The dma-buf feedback object to which the tranche is added |
| * @param format_table The dma-buf feedback formats table |
| * @param target_device The target device of the new tranche |
| * @param flags The flags of the new tranche |
| * @param preference The preference of the new tranche |
| * @return The tranche created, or NULL on failure |
| */ |
| WL_EXPORT struct weston_dmabuf_feedback_tranche * |
| weston_dmabuf_feedback_tranche_create(struct weston_dmabuf_feedback *dmabuf_feedback, |
| struct weston_dmabuf_feedback_format_table *format_table, |
| dev_t target_device, uint32_t flags, |
| enum weston_dmabuf_feedback_tranche_preference preference) |
| { |
| struct weston_dmabuf_feedback_tranche *tranche, *ptr; |
| struct wl_list *pos; |
| |
| tranche = zalloc(sizeof(*tranche)); |
| if (!tranche) { |
| weston_log("%s: out of memory\n", __func__); |
| return NULL; |
| } |
| |
| tranche->active = true; |
| tranche->target_device = target_device; |
| tranche->flags = flags; |
| tranche->preference = preference; |
| |
| /* Get the formats indices array */ |
| if (flags == 0) { |
| if (wl_array_copy(&tranche->formats_indices, |
| &format_table->renderer_formats_indices) < 0) { |
| weston_log("%s: out of memory\n", __func__); |
| goto err; |
| } |
| } else if (flags & ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT) { |
| if (wl_array_copy(&tranche->formats_indices, |
| &format_table->scanout_formats_indices) < 0) { |
| weston_log("%s: out of memory\n", __func__); |
| goto err; |
| } |
| } else { |
| weston_log("error: for now we just have renderer and scanout " |
| "tranches, can't create other type of tranche\n"); |
| goto err; |
| } |
| |
| /* The list of tranches is ordered by preference. |
| * Highest preference comes first. */ |
| pos = &dmabuf_feedback->tranche_list; |
| wl_list_for_each(ptr, &dmabuf_feedback->tranche_list, link) { |
| pos = &ptr->link; |
| if (ptr->preference <= tranche->preference) |
| break; |
| } |
| wl_list_insert(pos->prev, &tranche->link); |
| |
| return tranche; |
| |
| err: |
| free(tranche); |
| return NULL; |
| } |
| |
| static void |
| weston_dmabuf_feedback_tranche_destroy(struct weston_dmabuf_feedback_tranche *tranche) |
| { |
| wl_array_release(&tranche->formats_indices); |
| wl_list_remove(&tranche->link); |
| free(tranche); |
| } |
| |
| static int |
| format_table_add_renderer_formats(struct weston_dmabuf_feedback_format_table *format_table, |
| const struct weston_drm_format_array *renderer_formats) |
| { |
| struct weston_drm_format *fmt; |
| unsigned int num_modifiers; |
| const uint64_t *modifiers; |
| uint16_t index, *index_ptr; |
| unsigned int size; |
| unsigned int i; |
| |
| size = sizeof(index) * |
| weston_drm_format_array_count_pairs(renderer_formats); |
| |
| if (!wl_array_add(&format_table->renderer_formats_indices, size)) { |
| weston_log("%s: out of memory\n", __func__); |
| return -1; |
| } |
| |
| index = 0; |
| wl_array_for_each(fmt, &renderer_formats->arr) { |
| modifiers = weston_drm_format_get_modifiers(fmt, &num_modifiers); |
| for (i = 0; i < num_modifiers; i++) { |
| format_table->data[index].format = fmt->format; |
| format_table->data[index].modifier = modifiers[i]; |
| index++; |
| } |
| } |
| |
| index = 0; |
| wl_array_for_each(index_ptr, &format_table->renderer_formats_indices) |
| *index_ptr = index++; |
| |
| return 0; |
| } |
| |
| /** Creates dma-buf feedback format table |
| * |
| * @param renderer_formats The formats that compose the table |
| * @return The dma-buf feedback format table, or NULL on failure |
| */ |
| WL_EXPORT struct weston_dmabuf_feedback_format_table * |
| weston_dmabuf_feedback_format_table_create(const struct weston_drm_format_array *renderer_formats) |
| { |
| struct weston_dmabuf_feedback_format_table *format_table; |
| int ret; |
| |
| format_table = zalloc(sizeof(*format_table)); |
| if (!format_table) { |
| weston_log("%s: out of memory\n", __func__); |
| return NULL; |
| } |
| wl_array_init(&format_table->renderer_formats_indices); |
| wl_array_init(&format_table->scanout_formats_indices); |
| |
| /* Creates formats file table and mmap it */ |
| format_table->size = weston_drm_format_array_count_pairs(renderer_formats) * |
| sizeof(*format_table->data); |
| format_table->fd = os_create_anonymous_file(format_table->size); |
| if (format_table->fd < 0) { |
| weston_log("error: failed to create format table file: %s\n", |
| strerror(errno)); |
| goto err_fd; |
| } |
| format_table->data = mmap(NULL, format_table->size, PROT_READ | PROT_WRITE, |
| MAP_SHARED, format_table->fd, 0); |
| if (format_table->data == MAP_FAILED) { |
| weston_log("error: mmap for format table failed: %s\n", |
| strerror(errno)); |
| goto err_mmap; |
| } |
| |
| /* Add renderer formats to file table */ |
| ret = format_table_add_renderer_formats(format_table, renderer_formats); |
| if (ret < 0) |
| goto err_formats; |
| |
| return format_table; |
| |
| err_formats: |
| munmap(format_table->data, format_table->size); |
| err_mmap: |
| close(format_table->fd); |
| err_fd: |
| wl_array_release(&format_table->renderer_formats_indices); |
| free(format_table); |
| return NULL; |
| } |
| |
| /** Destroys dma-buf feedback formats table |
| * |
| * @param format_table The dma-buf feedback format table to destroy |
| */ |
| WL_EXPORT void |
| weston_dmabuf_feedback_format_table_destroy(struct weston_dmabuf_feedback_format_table *format_table) |
| { |
| wl_array_release(&format_table->renderer_formats_indices); |
| wl_array_release(&format_table->scanout_formats_indices); |
| |
| munmap(format_table->data, format_table->size); |
| close(format_table->fd); |
| |
| free(format_table); |
| } |
| |
| static int |
| format_table_get_format_index(struct weston_dmabuf_feedback_format_table *format_table, |
| uint32_t format, uint64_t modifier, uint16_t *index_out) |
| { |
| uint16_t index; |
| unsigned int num_elements = format_table->size / sizeof(index); |
| |
| for (index = 0; index < num_elements; index++) { |
| if (format_table->data[index].format == format && |
| format_table->data[index].modifier == modifier) { |
| *index_out = index; |
| return 0; |
| } |
| } |
| |
| return -1; |
| } |
| |
| /** Set scanout formats indices in the dma-buf feedback format table |
| * |
| * The table consists of the formats supported by the renderer. A dma-buf |
| * feedback scanout tranche consists of the union of the KMS plane's formats |
| * intersected with the renderer formats. With this function we compute the |
| * indices of these plane's formats in the table and save them in the |
| * table->scanout_formats_indices, allowing us to create scanout tranches. |
| * |
| * @param format_table The dma-buf feedback format table |
| * @param scanout_formats The scanout formats |
| * @return 0 on success, -1 on failure |
| */ |
| WL_EXPORT int |
| weston_dmabuf_feedback_format_table_set_scanout_indices(struct weston_dmabuf_feedback_format_table *format_table, |
| const struct weston_drm_format_array *scanout_formats) |
| { |
| struct weston_drm_format *fmt; |
| unsigned int num_modifiers; |
| const uint64_t *modifiers; |
| uint16_t index, *index_ptr; |
| unsigned int i; |
| int ret; |
| |
| wl_array_for_each(fmt, &scanout_formats->arr) { |
| modifiers = weston_drm_format_get_modifiers(fmt, &num_modifiers); |
| for (i = 0; i < num_modifiers; i++) { |
| index_ptr = |
| wl_array_add(&format_table->scanout_formats_indices, |
| sizeof(index)); |
| if (!index_ptr) |
| goto err; |
| |
| ret = format_table_get_format_index(format_table, fmt->format, |
| modifiers[i], &index); |
| if (ret < 0) |
| goto err; |
| |
| *index_ptr = index; |
| } |
| } |
| |
| return 0; |
| |
| err: |
| wl_array_release(&format_table->scanout_formats_indices); |
| wl_array_init(&format_table->scanout_formats_indices); |
| return -1; |
| } |
| |
| /** Creates dma-buf feedback object |
| * |
| * @param main_device The main device of the dma-buf feedback |
| * @return The dma-buf feedback object created, or NULL on failure |
| */ |
| WL_EXPORT struct weston_dmabuf_feedback * |
| weston_dmabuf_feedback_create(dev_t main_device) |
| { |
| struct weston_dmabuf_feedback *dmabuf_feedback; |
| |
| dmabuf_feedback = zalloc(sizeof(*dmabuf_feedback)); |
| if (!dmabuf_feedback) { |
| weston_log("%s: out of memory\n", __func__); |
| return NULL; |
| } |
| |
| dmabuf_feedback->main_device = main_device; |
| wl_list_init(&dmabuf_feedback->tranche_list); |
| wl_list_init(&dmabuf_feedback->resource_list); |
| |
| return dmabuf_feedback; |
| } |
| |
| /** Destroy dma-buf feedback object |
| * |
| * @param dmabuf_feedback The dma-buf feedback object to destroy |
| */ |
| WL_EXPORT void |
| weston_dmabuf_feedback_destroy(struct weston_dmabuf_feedback *dmabuf_feedback) |
| { |
| struct weston_dmabuf_feedback_tranche *tranche, *tranche_tmp; |
| struct wl_resource *res, *res_tmp; |
| |
| wl_list_for_each_safe(tranche, tranche_tmp, &dmabuf_feedback->tranche_list, link) |
| weston_dmabuf_feedback_tranche_destroy(tranche); |
| |
| wl_resource_for_each_safe(res, res_tmp, &dmabuf_feedback->resource_list) { |
| wl_list_remove(wl_resource_get_link(res)); |
| wl_list_init(wl_resource_get_link(res)); |
| } |
| |
| free(dmabuf_feedback); |
| } |
| |
| /** Find tranche in a dma-buf feedback object |
| * |
| * @param dmabuf_feedback The dma-buf feedback object where to look for |
| * @param target_device The target device of the tranche |
| * @param flags The flags of the tranche |
| * @param preference The preference of the tranche |
| * @return The tranche, or NULL if it was not found |
| */ |
| WL_EXPORT struct weston_dmabuf_feedback_tranche * |
| weston_dmabuf_feedback_find_tranche(struct weston_dmabuf_feedback *dmabuf_feedback, |
| dev_t target_device, uint32_t flags, |
| enum weston_dmabuf_feedback_tranche_preference preference) |
| { |
| struct weston_dmabuf_feedback_tranche *tranche; |
| |
| wl_list_for_each(tranche, &dmabuf_feedback->tranche_list, link) |
| if (tranche->target_device == target_device && |
| tranche->flags == flags && tranche->preference == preference) |
| return tranche; |
| |
| return NULL; |
| } |
| |
| static void |
| weston_dmabuf_feedback_send(struct weston_dmabuf_feedback *dmabuf_feedback, |
| struct weston_dmabuf_feedback_format_table *format_table, |
| struct wl_resource *res, bool advertise_format_table) |
| { |
| struct weston_dmabuf_feedback_tranche *tranche; |
| struct wl_array device; |
| dev_t *dev; |
| |
| /* main_device and target_device events need a dev_t as parameter, |
| * but we can't use this directly to communicate with the Wayland |
| * client. The solution is to use a wl_array, which is supported by |
| * Wayland, and add the dev_t as an element of the array. */ |
| wl_array_init(&device); |
| dev = wl_array_add(&device, sizeof(*dev)); |
| if (!dev) { |
| wl_resource_post_no_memory(res); |
| return; |
| } |
| |
| /* format_table event - In Weston, we never modify the dma-buf feedback |
| * format table. So we have this flag in order to advertise the format |
| * table only if the client has just subscribed to receive the events |
| * for this feedback object. When we need to re-send the feedback events |
| * for this client, the table event won't be sent. */ |
| if (advertise_format_table) |
| zwp_linux_dmabuf_feedback_v1_send_format_table(res, format_table->fd, |
| format_table->size); |
| |
| /* main_device event */ |
| *dev = dmabuf_feedback->main_device; |
| zwp_linux_dmabuf_feedback_v1_send_main_device(res, &device); |
| |
| /* send events for each tranche */ |
| wl_list_for_each(tranche, &dmabuf_feedback->tranche_list, link) { |
| if (!tranche->active) |
| continue; |
| |
| /* tranche_target_device event */ |
| *dev = tranche->target_device; |
| zwp_linux_dmabuf_feedback_v1_send_tranche_target_device(res, &device); |
| |
| /* tranche_flags event */ |
| zwp_linux_dmabuf_feedback_v1_send_tranche_flags(res, tranche->flags); |
| |
| /* tranche_formats event */ |
| zwp_linux_dmabuf_feedback_v1_send_tranche_formats(res, &tranche->formats_indices); |
| |
| /* tranche_done_event */ |
| zwp_linux_dmabuf_feedback_v1_send_tranche_done(res); |
| } |
| |
| /* compositor_done_event */ |
| zwp_linux_dmabuf_feedback_v1_send_done(res); |
| |
| wl_array_release(&device); |
| } |
| |
| /** Sends the feedback events for a dma-buf feedback object |
| * |
| * Given a dma-buf feedback object, this will send events to clients that are |
| * subscribed to it. This is useful for the per-surface dma-buf feedback, which |
| * is dynamic and can change throughout compositor's life. These changes results |
| * in the need to resend the feedback events to clients. |
| * |
| * @param dmabuf_feedback The weston_dmabuf_feedback object |
| * @param format_table The dma-buf feedback formats table |
| */ |
| WL_EXPORT void |
| weston_dmabuf_feedback_send_all(struct weston_dmabuf_feedback *dmabuf_feedback, |
| struct weston_dmabuf_feedback_format_table *format_table) |
| { |
| struct wl_resource *res; |
| |
| wl_resource_for_each(res, &dmabuf_feedback->resource_list) |
| weston_dmabuf_feedback_send(dmabuf_feedback, |
| format_table, res, false); |
| } |
| |
| static void |
| dmabuf_feedback_resource_destroy(struct wl_resource *resource) |
| { |
| wl_list_remove(wl_resource_get_link(resource)); |
| } |
| |
| static void |
| dmabuf_feedback_destroy(struct wl_client *client, struct wl_resource *resource) |
| { |
| wl_resource_destroy(resource); |
| } |
| |
| static const struct zwp_linux_dmabuf_feedback_v1_interface |
| zwp_linux_dmabuf_feedback_implementation = { |
| dmabuf_feedback_destroy |
| }; |
| |
| static struct wl_resource * |
| dmabuf_feedback_resource_create(struct wl_resource *dmabuf_resource, |
| struct wl_client *client, uint32_t dmabuf_feedback_id) |
| { |
| struct wl_resource *dmabuf_feedback_res; |
| uint32_t version; |
| |
| version = wl_resource_get_version(dmabuf_resource); |
| |
| dmabuf_feedback_res = |
| wl_resource_create(client, &zwp_linux_dmabuf_feedback_v1_interface, |
| version, dmabuf_feedback_id); |
| if (!dmabuf_feedback_res) |
| return NULL; |
| |
| wl_list_init(wl_resource_get_link(dmabuf_feedback_res)); |
| wl_resource_set_implementation(dmabuf_feedback_res, |
| &zwp_linux_dmabuf_feedback_implementation, |
| NULL, dmabuf_feedback_resource_destroy); |
| |
| return dmabuf_feedback_res; |
| } |
| |
| static void |
| linux_dmabuf_get_default_feedback(struct wl_client *client, |
| struct wl_resource *dmabuf_resource, |
| uint32_t dmabuf_feedback_id) |
| { |
| struct weston_compositor *compositor = |
| wl_resource_get_user_data(dmabuf_resource); |
| struct wl_resource *dmabuf_feedback_resource; |
| |
| dmabuf_feedback_resource = |
| dmabuf_feedback_resource_create(dmabuf_resource, |
| client, dmabuf_feedback_id); |
| if (!dmabuf_feedback_resource) { |
| wl_resource_post_no_memory(dmabuf_resource); |
| return; |
| } |
| |
| weston_dmabuf_feedback_send(compositor->default_dmabuf_feedback, |
| compositor->dmabuf_feedback_format_table, |
| dmabuf_feedback_resource, true); |
| } |
| |
| static void |
| linux_dmabuf_get_per_surface_feedback(struct wl_client *client, |
| struct wl_resource *dmabuf_resource, |
| uint32_t dmabuf_feedback_id, |
| struct wl_resource *surface_resource) |
| { |
| struct weston_surface *surface = |
| wl_resource_get_user_data(surface_resource); |
| struct wl_resource *dmabuf_feedback_resource; |
| |
| dmabuf_feedback_resource = |
| dmabuf_feedback_resource_create(dmabuf_resource, |
| client, dmabuf_feedback_id); |
| if (!dmabuf_feedback_resource) { |
| wl_resource_post_no_memory(dmabuf_resource); |
| return; |
| } |
| |
| /* Surface dma-buf feedback is dynamic and may need to be resent to |
| * clients when they change. So we need to keep the resources list */ |
| wl_list_insert(&surface->dmabuf_feedback->resource_list, |
| wl_resource_get_link(dmabuf_feedback_resource)); |
| |
| weston_dmabuf_feedback_send(surface->dmabuf_feedback, |
| surface->compositor->dmabuf_feedback_format_table, |
| dmabuf_feedback_resource, true); |
| } |
| |
| /** Get the linux_dmabuf_buffer from a wl_buffer resource |
| * |
| * If the given wl_buffer resource was created through the linux_dmabuf |
| * protocol interface, returns the linux_dmabuf_buffer object. This can |
| * be used as a type check for a wl_buffer. |
| * |
| * \param resource A wl_buffer resource. |
| * \return The linux_dmabuf_buffer if it exists, or NULL otherwise. |
| */ |
| WL_EXPORT struct linux_dmabuf_buffer * |
| linux_dmabuf_buffer_get(struct wl_resource *resource) |
| { |
| struct linux_dmabuf_buffer *buffer; |
| |
| if (!resource) |
| return NULL; |
| |
| if (!wl_resource_instance_of(resource, &wl_buffer_interface, |
| &linux_dmabuf_buffer_implementation)) |
| return NULL; |
| |
| buffer = wl_resource_get_user_data(resource); |
| assert(buffer); |
| assert(!buffer->params_resource); |
| assert(buffer->buffer_resource == resource); |
| |
| return buffer; |
| } |
| |
| /** Set renderer-private data |
| * |
| * Set the user data for the linux_dmabuf_buffer. It is invalid to overwrite |
| * a non-NULL user data with a new non-NULL pointer. This is meant to |
| * protect against renderers fighting over linux_dmabuf_buffer user data |
| * ownership. |
| * |
| * The renderer-private data is usually set from the |
| * weston_renderer::import_dmabuf hook. |
| * |
| * \param buffer The linux_dmabuf_buffer object to set for. |
| * \param data The new renderer-private data pointer. |
| * \param func Destructor function to be called for the renderer-private |
| * data when the linux_dmabuf_buffer gets destroyed. |
| * |
| * \sa weston_compositor_import_dmabuf |
| */ |
| WL_EXPORT void |
| linux_dmabuf_buffer_set_user_data(struct linux_dmabuf_buffer *buffer, |
| void *data, |
| dmabuf_user_data_destroy_func func) |
| { |
| assert(data == NULL || buffer->user_data == NULL); |
| |
| buffer->user_data = data; |
| buffer->user_data_destroy_func = func; |
| } |
| |
| /** Get renderer-private data |
| * |
| * Get the user data from the linux_dmabuf_buffer. |
| * |
| * \param buffer The linux_dmabuf_buffer to query. |
| * \return Renderer-private data pointer. |
| * |
| * \sa linux_dmabuf_buffer_set_user_data |
| */ |
| WL_EXPORT void * |
| linux_dmabuf_buffer_get_user_data(struct linux_dmabuf_buffer *buffer) |
| { |
| return buffer->user_data; |
| } |
| |
| static const struct zwp_linux_dmabuf_v1_interface linux_dmabuf_implementation = { |
| linux_dmabuf_destroy, |
| linux_dmabuf_create_params, |
| linux_dmabuf_get_default_feedback, |
| linux_dmabuf_get_per_surface_feedback |
| }; |
| |
| static void |
| bind_linux_dmabuf(struct wl_client *client, |
| void *data, uint32_t version, uint32_t id) |
| { |
| struct weston_compositor *compositor = data; |
| const struct weston_drm_format_array *supported_formats; |
| struct wl_resource *resource; |
| struct weston_drm_format *fmt; |
| const uint64_t *modifiers; |
| unsigned int num_modifiers; |
| unsigned int i; |
| |
| resource = wl_resource_create(client, &zwp_linux_dmabuf_v1_interface, |
| version, id); |
| if (resource == NULL) { |
| wl_client_post_no_memory(client); |
| return; |
| } |
| |
| wl_resource_set_implementation(resource, &linux_dmabuf_implementation, |
| compositor, NULL); |
| |
| /* Advertise formats/modifiers. From version 4 onwards, we should not send |
| * zwp_linux_dmabuf_v1_send_modifier and zwp_linux_dmabuf_v1_send_format |
| * events, instead we must send the dma-buf feedback events. */ |
| if (version >= 4) |
| return; |
| |
| /* If we got here, it means that the renderer is able to import dma-buf |
| * buffers, and so it must have get_supported_formats() set. */ |
| assert(compositor->renderer->get_supported_formats != NULL); |
| supported_formats = compositor->renderer->get_supported_formats(compositor); |
| |
| wl_array_for_each(fmt, &supported_formats->arr) { |
| modifiers = weston_drm_format_get_modifiers(fmt, &num_modifiers); |
| if (fmt->format == DRM_FORMAT_NV12 || fmt->format == DRM_FORMAT_NV21) { |
| if (version >= ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) |
| zwp_linux_dmabuf_v1_send_modifier(resource, fmt->format, 0, 0); |
| else |
| zwp_linux_dmabuf_v1_send_format(resource, fmt->format); |
| continue; |
| } |
| for (i = 0; i < num_modifiers; i++) { |
| if (version >= ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) { |
| uint32_t modifier_lo = modifiers[i] & 0xFFFFFFFF; |
| uint32_t modifier_hi = modifiers[i] >> 32; |
| zwp_linux_dmabuf_v1_send_modifier(resource, |
| fmt->format, |
| modifier_hi, |
| modifier_lo); |
| } else if (modifiers[i] == DRM_FORMAT_MOD_LINEAR || |
| modifiers[i] == DRM_FORMAT_MOD_INVALID) { |
| zwp_linux_dmabuf_v1_send_format(resource, |
| fmt->format); |
| } |
| } |
| } |
| } |
| |
| /** Advertise linux_dmabuf support |
| * |
| * Calling this initializes the zwp_linux_dmabuf protocol support, so that |
| * the interface will be advertised to clients. Essentially it creates a |
| * global. Do not call this function multiple times in the compositor's |
| * lifetime. There is no way to deinit explicitly, globals will be reaped |
| * when the wl_display gets destroyed. |
| * |
| * \param compositor The compositor to init for. |
| * \return Zero on success, -1 on failure. |
| */ |
| WL_EXPORT int |
| linux_dmabuf_setup(struct weston_compositor *compositor) |
| { |
| int max_version; |
| |
| /* If we were able to create the default dma-buf feedback for the |
| * compositor, that means that we are able to advertise dma-buf feedback |
| * events. In such case we support the version 4 of the protocol. */ |
| max_version = compositor->default_dmabuf_feedback ? 4 : 3; |
| |
| if (!wl_global_create(compositor->wl_display, |
| &zwp_linux_dmabuf_v1_interface, |
| max_version, compositor, bind_linux_dmabuf)) |
| return -1; |
| |
| return 0; |
| } |
| |
| /** Resolve an internal compositor error by disconnecting the client. |
| * |
| * This function is used in cases when the dmabuf-based wl_buffer |
| * turns out unusable and there is no fallback path. This is used by |
| * renderers which are the fallback path in the first place. |
| * |
| * It is possible the fault is caused by a compositor bug, the underlying |
| * graphics stack bug or normal behaviour, or perhaps a client mistake. |
| * In any case, the options are to either composite garbage or nothing, |
| * or disconnect the client. This is a helper function for the latter. |
| * |
| * The error is sent as an INVALID_OBJECT error on the client's wl_display. |
| * |
| * \param buffer The linux_dmabuf_buffer that is unusable. |
| * \param msg A custom error message attached to the protocol error. |
| */ |
| WL_EXPORT void |
| linux_dmabuf_buffer_send_server_error(struct linux_dmabuf_buffer *buffer, |
| const char *msg) |
| { |
| struct wl_client *client; |
| struct wl_resource *display_resource; |
| uint32_t id; |
| |
| assert(buffer->buffer_resource); |
| id = wl_resource_get_id(buffer->buffer_resource); |
| client = wl_resource_get_client(buffer->buffer_resource); |
| display_resource = wl_client_get_object(client, 1); |
| |
| assert(display_resource); |
| wl_resource_post_error(display_resource, |
| WL_DISPLAY_ERROR_INVALID_OBJECT, |
| "linux_dmabuf server error with " |
| "wl_buffer@%u: %s", id, msg); |
| } |