| // Copyright 2018 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "sommelier.h" // NOLINT(build/include_directory) |
| #include "sommelier-tracing.h" // NOLINT(build/include_directory) |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <gbm.h> |
| #include <libdrm/drm_fourcc.h> |
| #include <limits.h> |
| #include <linux/virtwl.h> |
| #include <pixman.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <unistd.h> |
| #include <wayland-client.h> |
| #include <wayland-util.h> |
| |
| #include "drm-server-protocol.h" // NOLINT(build/include_directory) |
| #include "linux-dmabuf-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) |
| #include "viewporter-client-protocol.h" // NOLINT(build/include_directory) |
| |
| #define MIN_SIZE (INT_MIN / 10) |
| #define MAX_SIZE (INT_MAX / 10) |
| |
| #define DMA_BUF_SYNC_READ (1 << 0) |
| #define DMA_BUF_SYNC_WRITE (2 << 0) |
| #define DMA_BUF_SYNC_RW (DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE) |
| #define DMA_BUF_SYNC_START (0 << 2) |
| #define DMA_BUF_SYNC_END (1 << 2) |
| |
| #define DMA_BUF_BASE 'b' |
| #define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync) |
| |
| struct sl_host_compositor { |
| struct sl_compositor* compositor; |
| struct wl_resource* resource; |
| struct wl_compositor* proxy; |
| }; |
| |
| struct sl_output_buffer { |
| struct wl_list link; |
| uint32_t width; |
| uint32_t height; |
| uint32_t format; |
| struct wl_buffer* internal; |
| struct sl_mmap* mmap; |
| struct pixman_region32 damage; |
| struct sl_host_surface* surface; |
| }; |
| |
| struct dma_buf_sync { |
| __u64 flags; |
| }; |
| |
| static void sl_dmabuf_sync(int fd, __u64 flags) { |
| struct dma_buf_sync sync = {0}; |
| int rv; |
| |
| sync.flags = flags; |
| do { |
| rv = ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync); |
| } while (rv == -1 && errno == EINTR); |
| } |
| |
| static void sl_dmabuf_begin_write(int fd) { |
| sl_dmabuf_sync(fd, DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE); |
| } |
| |
| static void sl_dmabuf_end_write(int fd) { |
| sl_dmabuf_sync(fd, DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE); |
| } |
| |
| static void sl_virtwl_dmabuf_sync(int fd, __u32 flags) { |
| struct virtwl_ioctl_dmabuf_sync sync = {0}; |
| int rv; |
| |
| sync.flags = flags; |
| rv = ioctl(fd, VIRTWL_IOCTL_DMABUF_SYNC, &sync); |
| assert(!rv); |
| UNUSED(rv); |
| } |
| |
| static void sl_virtwl_dmabuf_begin_write(int fd) { |
| sl_virtwl_dmabuf_sync(fd, DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE); |
| } |
| |
| static void sl_virtwl_dmabuf_end_write(int fd) { |
| sl_virtwl_dmabuf_sync(fd, DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE); |
| } |
| |
| static uint32_t sl_gbm_format_for_shm_format(uint32_t format) { |
| switch (format) { |
| case WL_SHM_FORMAT_NV12: |
| return GBM_FORMAT_NV12; |
| case WL_SHM_FORMAT_RGB565: |
| return GBM_FORMAT_RGB565; |
| case WL_SHM_FORMAT_ARGB8888: |
| return GBM_FORMAT_ARGB8888; |
| case WL_SHM_FORMAT_ABGR8888: |
| return GBM_FORMAT_ABGR8888; |
| case WL_SHM_FORMAT_XRGB8888: |
| return GBM_FORMAT_XRGB8888; |
| case WL_SHM_FORMAT_XBGR8888: |
| return GBM_FORMAT_XBGR8888; |
| } |
| assert(0); |
| return 0; |
| } |
| |
| static uint32_t sl_drm_format_for_shm_format(int format) { |
| switch (format) { |
| case WL_SHM_FORMAT_NV12: |
| return WL_DRM_FORMAT_NV12; |
| case WL_SHM_FORMAT_RGB565: |
| return WL_DRM_FORMAT_RGB565; |
| case WL_SHM_FORMAT_ARGB8888: |
| return WL_DRM_FORMAT_ARGB8888; |
| case WL_SHM_FORMAT_ABGR8888: |
| return WL_DRM_FORMAT_ABGR8888; |
| case WL_SHM_FORMAT_XRGB8888: |
| return WL_DRM_FORMAT_XRGB8888; |
| case WL_SHM_FORMAT_XBGR8888: |
| return WL_DRM_FORMAT_XBGR8888; |
| } |
| assert(0); |
| return 0; |
| } |
| |
| static void sl_output_buffer_destroy(struct sl_output_buffer* buffer) { |
| wl_buffer_destroy(buffer->internal); |
| sl_mmap_unref(buffer->mmap); |
| pixman_region32_fini(&buffer->damage); |
| wl_list_remove(&buffer->link); |
| free(buffer); |
| } |
| |
| static void sl_output_buffer_release(void* data, struct wl_buffer* buffer) { |
| TRACE_EVENT("surface", "sl_output_buffer_release"); |
| struct sl_output_buffer* output_buffer = |
| static_cast<sl_output_buffer*>(wl_buffer_get_user_data(buffer)); |
| struct sl_host_surface* host_surface = output_buffer->surface; |
| |
| wl_list_remove(&output_buffer->link); |
| wl_list_insert(&host_surface->released_buffers, &output_buffer->link); |
| } |
| |
| static const struct wl_buffer_listener sl_output_buffer_listener = { |
| sl_output_buffer_release}; |
| |
| static void sl_host_surface_destroy(struct wl_client* client, |
| struct wl_resource* resource) { |
| wl_resource_destroy(resource); |
| } |
| |
| static void sl_host_surface_attach(struct wl_client* client, |
| struct wl_resource* resource, |
| struct wl_resource* buffer_resource, |
| int32_t x, |
| int32_t y) { |
| struct sl_host_surface* host = |
| static_cast<sl_host_surface*>(wl_resource_get_user_data(resource)); |
| struct sl_host_buffer* host_buffer = |
| buffer_resource ? static_cast<sl_host_buffer*>( |
| wl_resource_get_user_data(buffer_resource)) |
| : NULL; |
| struct wl_buffer* buffer_proxy = NULL; |
| struct sl_window* window; |
| double scale = host->ctx->scale; |
| |
| host->current_buffer = NULL; |
| if (host->contents_shm_mmap) { |
| sl_mmap_unref(host->contents_shm_mmap); |
| host->contents_shm_mmap = NULL; |
| } |
| |
| if (host_buffer) { |
| host->contents_width = host_buffer->width; |
| host->contents_height = host_buffer->height; |
| buffer_proxy = host_buffer->proxy; |
| if (host_buffer->shm_mmap) |
| host->contents_shm_mmap = sl_mmap_ref(host_buffer->shm_mmap); |
| } |
| |
| if (host->contents_shm_mmap) { |
| while (!wl_list_empty(&host->released_buffers)) { |
| host->current_buffer = wl_container_of(host->released_buffers.next, |
| host->current_buffer, link); |
| |
| if (host->current_buffer->width == host_buffer->width && |
| host->current_buffer->height == host_buffer->height && |
| host->current_buffer->format == host_buffer->shm_format) { |
| break; |
| } |
| |
| sl_output_buffer_destroy(host->current_buffer); |
| host->current_buffer = NULL; |
| } |
| |
| // Allocate new output buffer. |
| if (!host->current_buffer) { |
| size_t width = host_buffer->width; |
| size_t height = host_buffer->height; |
| uint32_t shm_format = host_buffer->shm_format; |
| size_t bpp = sl_shm_bpp_for_shm_format(shm_format); |
| size_t num_planes = sl_shm_num_planes_for_shm_format(shm_format); |
| |
| host->current_buffer = static_cast<sl_output_buffer*>( |
| malloc(sizeof(struct sl_output_buffer))); |
| assert(host->current_buffer); |
| wl_list_insert(&host->released_buffers, &host->current_buffer->link); |
| host->current_buffer->width = width; |
| host->current_buffer->height = height; |
| host->current_buffer->format = shm_format; |
| host->current_buffer->surface = host; |
| pixman_region32_init_rect(&host->current_buffer->damage, 0, 0, MAX_SIZE, |
| MAX_SIZE); |
| |
| switch (host->ctx->shm_driver) { |
| case SHM_DRIVER_DMABUF: { |
| struct zwp_linux_buffer_params_v1* buffer_params; |
| struct gbm_bo* bo; |
| int stride0; |
| int fd; |
| |
| bo = gbm_bo_create(host->ctx->gbm, width, height, |
| sl_gbm_format_for_shm_format(shm_format), |
| GBM_BO_USE_SCANOUT | GBM_BO_USE_LINEAR); |
| stride0 = gbm_bo_get_stride(bo); |
| fd = gbm_bo_get_fd(bo); |
| |
| buffer_params = zwp_linux_dmabuf_v1_create_params( |
| host->ctx->linux_dmabuf->internal); |
| zwp_linux_buffer_params_v1_add(buffer_params, fd, 0, 0, stride0, |
| DRM_FORMAT_MOD_INVALID >> 32, |
| DRM_FORMAT_MOD_INVALID & 0xffffffff); |
| host->current_buffer->internal = |
| zwp_linux_buffer_params_v1_create_immed( |
| buffer_params, width, height, |
| sl_drm_format_for_shm_format(shm_format), 0); |
| zwp_linux_buffer_params_v1_destroy(buffer_params); |
| |
| host->current_buffer->mmap = sl_mmap_create( |
| fd, height * stride0, bpp, 1, 0, stride0, 0, 0, 1, 0); |
| host->current_buffer->mmap->begin_write = sl_dmabuf_begin_write; |
| host->current_buffer->mmap->end_write = sl_dmabuf_end_write; |
| |
| gbm_bo_destroy(bo); |
| } break; |
| case SHM_DRIVER_VIRTWL: { |
| size_t size = host_buffer->shm_mmap->size; |
| struct virtwl_ioctl_new ioctl_new = {}; |
| ioctl_new.type = VIRTWL_IOCTL_NEW_ALLOC; |
| ioctl_new.fd = -1; |
| ioctl_new.flags = 0; |
| ioctl_new.size = static_cast<__u32>(size); |
| struct wl_shm_pool* pool; |
| int rv; |
| |
| rv = ioctl(host->ctx->virtwl_fd, VIRTWL_IOCTL_NEW, &ioctl_new); |
| assert(rv == 0); |
| UNUSED(rv); |
| |
| pool = |
| wl_shm_create_pool(host->ctx->shm->internal, ioctl_new.fd, size); |
| host->current_buffer->internal = wl_shm_pool_create_buffer( |
| pool, 0, width, height, host_buffer->shm_mmap->stride[0], |
| shm_format); |
| wl_shm_pool_destroy(pool); |
| |
| host->current_buffer->mmap = sl_mmap_create( |
| ioctl_new.fd, size, bpp, num_planes, 0, |
| host_buffer->shm_mmap->stride[0], |
| host_buffer->shm_mmap->offset[1] - |
| host_buffer->shm_mmap->offset[0], |
| host_buffer->shm_mmap->stride[1], host_buffer->shm_mmap->y_ss[0], |
| host_buffer->shm_mmap->y_ss[1]); |
| } break; |
| case SHM_DRIVER_VIRTWL_DMABUF: { |
| uint32_t drm_format = sl_drm_format_for_shm_format(shm_format); |
| struct virtwl_ioctl_new ioctl_new = {}; |
| ioctl_new.type = VIRTWL_IOCTL_NEW_DMABUF; |
| ioctl_new.fd = -1; |
| ioctl_new.flags = 0; |
| ioctl_new.dmabuf.width = static_cast<__u32>(width); |
| ioctl_new.dmabuf.height = static_cast<__u32>(height); |
| ioctl_new.dmabuf.format = drm_format; |
| struct zwp_linux_buffer_params_v1* buffer_params; |
| size_t size; |
| int rv; |
| |
| rv = ioctl(host->ctx->virtwl_fd, VIRTWL_IOCTL_NEW, &ioctl_new); |
| if (rv) { |
| fprintf(stderr, "error: virtwl dmabuf allocation failed: %s\n", |
| strerror(errno)); |
| _exit(EXIT_FAILURE); |
| } |
| |
| size = ioctl_new.dmabuf.stride0 * height; |
| buffer_params = zwp_linux_dmabuf_v1_create_params( |
| host->ctx->linux_dmabuf->internal); |
| zwp_linux_buffer_params_v1_add(buffer_params, ioctl_new.fd, 0, |
| ioctl_new.dmabuf.offset0, |
| ioctl_new.dmabuf.stride0, 0, 0); |
| if (num_planes > 1) { |
| zwp_linux_buffer_params_v1_add(buffer_params, ioctl_new.fd, 1, |
| ioctl_new.dmabuf.offset1, |
| ioctl_new.dmabuf.stride1, 0, 0); |
| size = MAX(size, ioctl_new.dmabuf.offset1 + |
| ioctl_new.dmabuf.stride1 * height / |
| host_buffer->shm_mmap->y_ss[1]); |
| } |
| host->current_buffer->internal = |
| zwp_linux_buffer_params_v1_create_immed(buffer_params, width, |
| height, drm_format, 0); |
| zwp_linux_buffer_params_v1_destroy(buffer_params); |
| |
| host->current_buffer->mmap = sl_mmap_create( |
| ioctl_new.fd, size, bpp, num_planes, ioctl_new.dmabuf.offset0, |
| ioctl_new.dmabuf.stride0, ioctl_new.dmabuf.offset1, |
| ioctl_new.dmabuf.stride1, host_buffer->shm_mmap->y_ss[0], |
| host_buffer->shm_mmap->y_ss[1]); |
| host->current_buffer->mmap->begin_write = |
| sl_virtwl_dmabuf_begin_write; |
| host->current_buffer->mmap->end_write = sl_virtwl_dmabuf_end_write; |
| } break; |
| } |
| |
| assert(host->current_buffer->internal); |
| assert(host->current_buffer->mmap); |
| |
| wl_buffer_set_user_data(host->current_buffer->internal, |
| host->current_buffer); |
| wl_buffer_add_listener(host->current_buffer->internal, |
| &sl_output_buffer_listener, host->current_buffer); |
| } |
| } |
| |
| x /= scale; |
| y /= scale; |
| |
| // TODO(davidriley): This should be done in the commit. |
| if (host_buffer && host_buffer->sync_point) { |
| host_buffer->sync_point->sync(host->ctx, host_buffer->sync_point); |
| } |
| |
| if (host->current_buffer) { |
| assert(host->current_buffer->internal); |
| wl_surface_attach(host->proxy, host->current_buffer->internal, x, y); |
| } else { |
| wl_surface_attach(host->proxy, buffer_proxy, x, y); |
| } |
| |
| wl_list_for_each(window, &host->ctx->windows, link) { |
| if (window->host_surface_id == wl_resource_get_id(resource)) { |
| while (sl_process_pending_configure_acks(window, host)) |
| continue; |
| |
| break; |
| } |
| } |
| } // NOLINT(whitespace/indent) |
| |
| static void sl_host_surface_damage(struct wl_client* client, |
| struct wl_resource* resource, |
| int32_t x, |
| int32_t y, |
| int32_t width, |
| int32_t height) { |
| struct sl_host_surface* host = |
| static_cast<sl_host_surface*>(wl_resource_get_user_data(resource)); |
| double scale = host->ctx->scale; |
| struct sl_output_buffer* buffer; |
| int64_t x1, y1, x2, y2; |
| |
| wl_list_for_each(buffer, &host->busy_buffers, link) { |
| pixman_region32_union_rect(&buffer->damage, &buffer->damage, x, y, width, |
| height); |
| } |
| wl_list_for_each(buffer, &host->released_buffers, link) { |
| pixman_region32_union_rect(&buffer->damage, &buffer->damage, x, y, width, |
| height); |
| } |
| |
| x1 = x; |
| y1 = y; |
| x2 = x1 + width; |
| y2 = y1 + height; |
| |
| // Enclosing rect after scaling and outset by one pixel to account for |
| // potential filtering. |
| x1 = MAX(MIN_SIZE, x1 - 1) / scale; |
| y1 = MAX(MIN_SIZE, y1 - 1) / scale; |
| x2 = ceil(MIN(x2 + 1, MAX_SIZE) / scale); |
| y2 = ceil(MIN(y2 + 1, MAX_SIZE) / scale); |
| |
| wl_surface_damage(host->proxy, x1, y1, x2 - x1, y2 - y1); |
| } |
| |
| static void sl_frame_callback_done(void* data, |
| struct wl_callback* callback, |
| uint32_t time) { |
| struct sl_host_callback* host = |
| static_cast<sl_host_callback*>(wl_callback_get_user_data(callback)); |
| |
| wl_callback_send_done(host->resource, time); |
| wl_resource_destroy(host->resource); |
| } |
| |
| static const struct wl_callback_listener sl_frame_callback_listener = { |
| sl_frame_callback_done}; |
| |
| static void sl_host_callback_destroy(struct wl_resource* resource) { |
| struct sl_host_callback* host = |
| static_cast<sl_host_callback*>(wl_resource_get_user_data(resource)); |
| |
| wl_callback_destroy(host->proxy); |
| wl_resource_set_user_data(resource, NULL); |
| free(host); |
| } |
| |
| static void sl_host_surface_frame(struct wl_client* client, |
| struct wl_resource* resource, |
| uint32_t callback) { |
| struct sl_host_surface* host = |
| static_cast<sl_host_surface*>(wl_resource_get_user_data(resource)); |
| struct sl_host_callback* host_callback = |
| static_cast<sl_host_callback*>(malloc(sizeof(*host_callback))); |
| assert(host_callback); |
| |
| host_callback->resource = |
| wl_resource_create(client, &wl_callback_interface, 1, callback); |
| wl_resource_set_implementation(host_callback->resource, NULL, host_callback, |
| sl_host_callback_destroy); |
| host_callback->proxy = wl_surface_frame(host->proxy); |
| wl_callback_set_user_data(host_callback->proxy, host_callback); |
| wl_callback_add_listener(host_callback->proxy, &sl_frame_callback_listener, |
| host_callback); |
| } |
| |
| static void sl_host_surface_set_opaque_region( |
| struct wl_client* client, |
| struct wl_resource* resource, |
| struct wl_resource* region_resource) { |
| struct sl_host_surface* host = |
| static_cast<sl_host_surface*>(wl_resource_get_user_data(resource)); |
| struct sl_host_region* host_region = |
| region_resource ? static_cast<sl_host_region*>( |
| wl_resource_get_user_data(region_resource)) |
| : NULL; |
| |
| wl_surface_set_opaque_region(host->proxy, |
| host_region ? host_region->proxy : NULL); |
| } // NOLINT(whitespace/indent) |
| |
| static void sl_host_surface_set_input_region( |
| struct wl_client* client, |
| struct wl_resource* resource, |
| struct wl_resource* region_resource) { |
| struct sl_host_surface* host = |
| static_cast<sl_host_surface*>(wl_resource_get_user_data(resource)); |
| struct sl_host_region* host_region = |
| region_resource ? static_cast<sl_host_region*>( |
| wl_resource_get_user_data(region_resource)) |
| : NULL; |
| |
| wl_surface_set_input_region(host->proxy, |
| host_region ? host_region->proxy : NULL); |
| } // NOLINT(whitespace/indent) |
| |
| static void sl_host_surface_commit(struct wl_client* client, |
| struct wl_resource* resource) { |
| struct sl_host_surface* host = |
| static_cast<sl_host_surface*>(wl_resource_get_user_data(resource)); |
| struct sl_viewport* viewport = NULL; |
| struct sl_window* window; |
| |
| TRACE_EVENT("surface", "sl_host_surface_commit"); |
| |
| if (!wl_list_empty(&host->contents_viewport)) |
| viewport = wl_container_of(host->contents_viewport.next, viewport, link); |
| |
| if (host->contents_shm_mmap) { |
| uint8_t* src_addr = static_cast<uint8_t*>(host->contents_shm_mmap->addr); |
| uint8_t* dst_addr = static_cast<uint8_t*>(host->current_buffer->mmap->addr); |
| size_t* src_offset = host->contents_shm_mmap->offset; |
| size_t* dst_offset = host->current_buffer->mmap->offset; |
| size_t* src_stride = host->contents_shm_mmap->stride; |
| size_t* dst_stride = host->current_buffer->mmap->stride; |
| size_t* y_ss = host->contents_shm_mmap->y_ss; |
| size_t bpp = host->contents_shm_mmap->bpp; |
| size_t num_planes = host->contents_shm_mmap->num_planes; |
| double contents_scale_x = host->contents_scale; |
| double contents_scale_y = host->contents_scale; |
| double contents_offset_x = 0.0; |
| double contents_offset_y = 0.0; |
| pixman_box32_t* rect; |
| int n; |
| |
| // Determine scale and offset for damage based on current viewport. |
| if (viewport) { |
| double contents_width = host->contents_width; |
| double contents_height = host->contents_height; |
| |
| if (viewport->src_x >= 0 && viewport->src_y >= 0) { |
| contents_offset_x = wl_fixed_to_double(viewport->src_x); |
| contents_offset_y = wl_fixed_to_double(viewport->src_y); |
| } |
| |
| if (viewport->dst_width > 0 && viewport->dst_height > 0) { |
| contents_scale_x *= contents_width / viewport->dst_width; |
| contents_scale_y *= contents_height / viewport->dst_height; |
| |
| // Take source rectangle into account when both destionation size and |
| // source rectangle are set. If only source rectangle is set, then |
| // it determines the surface size so it can be ignored. |
| if (viewport->src_width >= 0 && viewport->src_height >= 0) { |
| contents_scale_x *= |
| wl_fixed_to_double(viewport->src_width) / contents_width; |
| contents_scale_y *= |
| wl_fixed_to_double(viewport->src_height) / contents_height; |
| } |
| } |
| } |
| |
| if (host->current_buffer->mmap->begin_write) |
| host->current_buffer->mmap->begin_write(host->current_buffer->mmap->fd); |
| |
| rect = pixman_region32_rectangles(&host->current_buffer->damage, &n); |
| while (n--) { |
| int32_t x1, y1, x2, y2; |
| |
| // Enclosing rect after applying scale and offset. |
| x1 = rect->x1 * contents_scale_x + contents_offset_x; |
| y1 = rect->y1 * contents_scale_y + contents_offset_y; |
| x2 = rect->x2 * contents_scale_x + contents_offset_x + 0.5; |
| y2 = rect->y2 * contents_scale_y + contents_offset_y + 0.5; |
| |
| x1 = MAX(0, x1); |
| y1 = MAX(0, y1); |
| x2 = MIN(static_cast<int32_t>(host->contents_width), x2); |
| y2 = MIN(static_cast<int32_t>(host->contents_height), y2); |
| |
| if (x1 < x2 && y1 < y2) { |
| size_t i; |
| |
| for (i = 0; i < num_planes; ++i) { |
| uint8_t* src_base = src_addr + src_offset[i]; |
| uint8_t* dst_base = dst_addr + dst_offset[i]; |
| uint8_t* src = src_base + y1 * src_stride[i] + x1 * bpp; |
| uint8_t* dst = dst_base + y1 * dst_stride[i] + x1 * bpp; |
| int32_t width = x2 - x1; |
| int32_t height = (y2 - y1) / y_ss[i]; |
| size_t bytes = width * bpp; |
| |
| while (height--) { |
| memcpy(dst, src, bytes); |
| dst += dst_stride[i]; |
| src += src_stride[i]; |
| } |
| } |
| } |
| |
| ++rect; |
| } |
| |
| if (host->current_buffer->mmap->end_write) |
| host->current_buffer->mmap->end_write(host->current_buffer->mmap->fd); |
| |
| pixman_region32_clear(&host->current_buffer->damage); |
| |
| wl_list_remove(&host->current_buffer->link); |
| wl_list_insert(&host->busy_buffers, &host->current_buffer->link); |
| } |
| |
| if (host->contents_width && host->contents_height) { |
| double scale = host->ctx->scale * host->contents_scale; |
| |
| if (host->viewport) { |
| int width = host->contents_width; |
| int height = host->contents_height; |
| |
| // We need to take the client's viewport into account while still |
| // making sure our scale is accounted for. |
| if (viewport) { |
| if (viewport->src_x >= 0 && viewport->src_y >= 0 && |
| viewport->src_width >= 0 && viewport->src_height >= 0) { |
| wp_viewport_set_source(host->viewport, viewport->src_x, |
| viewport->src_y, viewport->src_width, |
| viewport->src_height); |
| |
| // If the source rectangle is set and the destination size is not |
| // set, then src_width and src_height should be integers, and the |
| // surface size becomes the source rectangle size. |
| width = wl_fixed_to_int(viewport->src_width); |
| height = wl_fixed_to_int(viewport->src_height); |
| } |
| |
| // Use destination size as surface size when set. |
| if (viewport->dst_width >= 0 && viewport->dst_height >= 0) { |
| width = viewport->dst_width; |
| height = viewport->dst_height; |
| } |
| } |
| |
| wp_viewport_set_destination(host->viewport, ceil(width / scale), |
| ceil(height / scale)); |
| } else { |
| wl_surface_set_buffer_scale(host->proxy, scale); |
| } |
| } |
| |
| // No need to defer client commits if surface has a role. E.g. is a cursor |
| // or shell surface. |
| if (host->has_role) { |
| wl_surface_commit(host->proxy); |
| |
| // GTK determines the scale based on the output the surface has entered. |
| // If the surface has not entered any output, then have it enter the |
| // internal output. TODO(reveman): Remove this when surface-output tracking |
| // has been implemented in Chrome. |
| if (!host->has_output) { |
| struct sl_host_output* output; |
| |
| wl_list_for_each(output, &host->ctx->host_outputs, link) { |
| if (output->internal) { |
| wl_surface_send_enter(host->resource, output->resource); |
| host->has_output = 1; |
| break; |
| } |
| } |
| } |
| } else { |
| // Commit if surface is associated with a window. Otherwise, defer |
| // commit until window is created. |
| wl_list_for_each(window, &host->ctx->windows, link) { |
| if (window->host_surface_id == wl_resource_get_id(resource)) { |
| if (window->xdg_surface) { |
| wl_surface_commit(host->proxy); |
| if (host->contents_width && host->contents_height) |
| window->realized = 1; |
| } |
| break; |
| } |
| } |
| } |
| |
| if (host->contents_shm_mmap) { |
| if (host->contents_shm_mmap->buffer_resource) { |
| TRACE_EVENT("surface", "sl_host_surface_commit wl_buffer_send_release"); |
| wl_buffer_send_release(host->contents_shm_mmap->buffer_resource); |
| } |
| sl_mmap_unref(host->contents_shm_mmap); |
| host->contents_shm_mmap = NULL; |
| } |
| } |
| |
| static void sl_host_surface_set_buffer_transform(struct wl_client* client, |
| struct wl_resource* resource, |
| int32_t transform) { |
| struct sl_host_surface* host = |
| static_cast<sl_host_surface*>(wl_resource_get_user_data(resource)); |
| |
| wl_surface_set_buffer_transform(host->proxy, transform); |
| } |
| |
| static void sl_host_surface_set_buffer_scale(struct wl_client* client, |
| struct wl_resource* resource, |
| int32_t scale) { |
| struct sl_host_surface* host = |
| static_cast<sl_host_surface*>(wl_resource_get_user_data(resource)); |
| |
| host->contents_scale = scale; |
| } |
| |
| static void sl_host_surface_damage_buffer(struct wl_client* client, |
| struct wl_resource* resource, |
| int32_t x, |
| int32_t y, |
| int32_t width, |
| int32_t height) { |
| assert(0); |
| } |
| |
| static const struct wl_surface_interface sl_surface_implementation = { |
| sl_host_surface_destroy, |
| sl_host_surface_attach, |
| sl_host_surface_damage, |
| sl_host_surface_frame, |
| sl_host_surface_set_opaque_region, |
| sl_host_surface_set_input_region, |
| sl_host_surface_commit, |
| sl_host_surface_set_buffer_transform, |
| sl_host_surface_set_buffer_scale, |
| sl_host_surface_damage_buffer}; |
| |
| static void sl_destroy_host_surface(struct wl_resource* resource) { |
| struct sl_host_surface* host = |
| static_cast<sl_host_surface*>(wl_resource_get_user_data(resource)); |
| struct sl_window *window, *surface_window = NULL; |
| struct sl_output_buffer* buffer; |
| |
| wl_list_for_each(window, &host->ctx->windows, link) { |
| if (window->host_surface_id == wl_resource_get_id(resource)) { |
| surface_window = window; |
| break; |
| } |
| } |
| |
| if (surface_window) { |
| surface_window->host_surface_id = 0; |
| sl_window_update(surface_window); |
| } |
| |
| if (host->contents_shm_mmap) |
| sl_mmap_unref(host->contents_shm_mmap); |
| |
| while (!wl_list_empty(&host->released_buffers)) { |
| buffer = wl_container_of(host->released_buffers.next, buffer, link); |
| sl_output_buffer_destroy(buffer); |
| } |
| while (!wl_list_empty(&host->busy_buffers)) { |
| buffer = wl_container_of(host->busy_buffers.next, buffer, link); |
| sl_output_buffer_destroy(buffer); |
| } |
| while (!wl_list_empty(&host->contents_viewport)) |
| wl_list_remove(host->contents_viewport.next); |
| |
| if (host->viewport) |
| wp_viewport_destroy(host->viewport); |
| wl_surface_destroy(host->proxy); |
| wl_resource_set_user_data(resource, NULL); |
| free(host); |
| } |
| |
| static void sl_surface_enter(void* data, |
| struct wl_surface* surface, |
| struct wl_output* output) { |
| struct sl_host_surface* host = |
| static_cast<sl_host_surface*>(wl_surface_get_user_data(surface)); |
| struct sl_host_output* host_output = |
| static_cast<sl_host_output*>(wl_output_get_user_data(output)); |
| |
| wl_surface_send_enter(host->resource, host_output->resource); |
| host->has_output = 1; |
| } |
| |
| static void sl_surface_leave(void* data, |
| struct wl_surface* surface, |
| struct wl_output* output) { |
| struct sl_host_surface* host = |
| static_cast<sl_host_surface*>(wl_surface_get_user_data(surface)); |
| struct sl_host_output* host_output = |
| static_cast<sl_host_output*>(wl_output_get_user_data(output)); |
| |
| wl_surface_send_leave(host->resource, host_output->resource); |
| } |
| |
| static const struct wl_surface_listener sl_surface_listener = { |
| sl_surface_enter, sl_surface_leave}; |
| |
| static void sl_region_destroy(struct wl_client* client, |
| struct wl_resource* resource) { |
| wl_resource_destroy(resource); |
| } |
| |
| static void sl_region_add(struct wl_client* client, |
| struct wl_resource* resource, |
| int32_t x, |
| int32_t y, |
| int32_t width, |
| int32_t height) { |
| struct sl_host_region* host = |
| static_cast<sl_host_region*>(wl_resource_get_user_data(resource)); |
| double scale = host->ctx->scale; |
| int32_t x1, y1, x2, y2; |
| |
| x1 = x / scale; |
| y1 = y / scale; |
| x2 = (x + width) / scale; |
| y2 = (y + height) / scale; |
| |
| wl_region_add(host->proxy, x1, y1, x2 - x1, y2 - y1); |
| } |
| |
| static void sl_region_subtract(struct wl_client* client, |
| struct wl_resource* resource, |
| int32_t x, |
| int32_t y, |
| int32_t width, |
| int32_t height) { |
| struct sl_host_region* host = |
| static_cast<sl_host_region*>(wl_resource_get_user_data(resource)); |
| double scale = host->ctx->scale; |
| int32_t x1, y1, x2, y2; |
| |
| x1 = x / scale; |
| y1 = y / scale; |
| x2 = (x + width) / scale; |
| y2 = (y + height) / scale; |
| |
| wl_region_subtract(host->proxy, x1, y1, x2 - x1, y2 - y1); |
| } |
| |
| static const struct wl_region_interface sl_region_implementation = { |
| sl_region_destroy, sl_region_add, sl_region_subtract}; |
| |
| static void sl_destroy_host_region(struct wl_resource* resource) { |
| struct sl_host_region* host = |
| static_cast<sl_host_region*>(wl_resource_get_user_data(resource)); |
| |
| wl_region_destroy(host->proxy); |
| wl_resource_set_user_data(resource, NULL); |
| free(host); |
| } |
| |
| static void sl_compositor_create_host_surface(struct wl_client* client, |
| struct wl_resource* resource, |
| uint32_t id) { |
| struct sl_host_compositor* host = |
| static_cast<sl_host_compositor*>(wl_resource_get_user_data(resource)); |
| struct sl_window *window, *unpaired_window = NULL; |
| struct sl_host_surface* host_surface = |
| static_cast<sl_host_surface*>(malloc(sizeof(*host_surface))); |
| assert(host_surface); |
| |
| host_surface->ctx = host->compositor->ctx; |
| host_surface->contents_width = 0; |
| host_surface->contents_height = 0; |
| host_surface->contents_scale = 1; |
| wl_list_init(&host_surface->contents_viewport); |
| host_surface->contents_shm_mmap = NULL; |
| host_surface->has_role = 0; |
| host_surface->has_output = 0; |
| host_surface->last_event_serial = 0; |
| host_surface->current_buffer = NULL; |
| wl_list_init(&host_surface->released_buffers); |
| wl_list_init(&host_surface->busy_buffers); |
| host_surface->resource = wl_resource_create( |
| client, &wl_surface_interface, wl_resource_get_version(resource), id); |
| wl_resource_set_implementation(host_surface->resource, |
| &sl_surface_implementation, host_surface, |
| sl_destroy_host_surface); |
| host_surface->proxy = wl_compositor_create_surface(host->proxy); |
| wl_surface_set_user_data(host_surface->proxy, host_surface); |
| wl_surface_add_listener(host_surface->proxy, &sl_surface_listener, |
| host_surface); |
| host_surface->viewport = NULL; |
| if (host_surface->ctx->viewporter) { |
| host_surface->viewport = wp_viewporter_get_viewport( |
| host_surface->ctx->viewporter->internal, host_surface->proxy); |
| } |
| |
| wl_list_for_each(window, &host->compositor->ctx->unpaired_windows, link) { |
| if (window->host_surface_id == id) { |
| unpaired_window = window; |
| break; |
| } |
| } |
| |
| if (unpaired_window) |
| sl_window_update(window); |
| } |
| |
| static void sl_compositor_create_host_region(struct wl_client* client, |
| struct wl_resource* resource, |
| uint32_t id) { |
| struct sl_host_compositor* host = |
| static_cast<sl_host_compositor*>(wl_resource_get_user_data(resource)); |
| struct sl_host_region* host_region = |
| static_cast<sl_host_region*>(malloc(sizeof(*host_region))); |
| assert(host_region); |
| |
| host_region->ctx = host->compositor->ctx; |
| host_region->resource = wl_resource_create( |
| client, &wl_region_interface, wl_resource_get_version(resource), id); |
| wl_resource_set_implementation(host_region->resource, |
| &sl_region_implementation, host_region, |
| sl_destroy_host_region); |
| host_region->proxy = wl_compositor_create_region(host->proxy); |
| wl_region_set_user_data(host_region->proxy, host_region); |
| } |
| |
| static const struct wl_compositor_interface sl_compositor_implementation = { |
| sl_compositor_create_host_surface, sl_compositor_create_host_region}; |
| |
| static void sl_destroy_host_compositor(struct wl_resource* resource) { |
| struct sl_host_compositor* host = |
| static_cast<sl_host_compositor*>(wl_resource_get_user_data(resource)); |
| |
| wl_compositor_destroy(host->proxy); |
| wl_resource_set_user_data(resource, NULL); |
| free(host); |
| } |
| |
| static void sl_bind_host_compositor(struct wl_client* client, |
| void* data, |
| uint32_t version, |
| uint32_t id) { |
| struct sl_context* ctx = (struct sl_context*)data; |
| struct sl_host_compositor* host = |
| static_cast<sl_host_compositor*>(malloc(sizeof(*host))); |
| assert(host); |
| host->compositor = ctx->compositor; |
| host->resource = |
| wl_resource_create(client, &wl_compositor_interface, |
| MIN(version, ctx->compositor->version), id); |
| wl_resource_set_implementation(host->resource, &sl_compositor_implementation, |
| host, sl_destroy_host_compositor); |
| host->proxy = static_cast<wl_compositor*>(wl_registry_bind( |
| wl_display_get_registry(ctx->display), ctx->compositor->id, |
| &wl_compositor_interface, ctx->compositor->version)); |
| wl_compositor_set_user_data(host->proxy, host); |
| } |
| |
| struct sl_global* sl_compositor_global_create(struct sl_context* ctx) { |
| return sl_global_create(ctx, &wl_compositor_interface, |
| ctx->compositor->version, ctx, |
| sl_bind_host_compositor); |
| } |