diff --git a/include/wlr/types/wlr_ext_image_capture_source_v1.h b/include/wlr/types/wlr_ext_image_capture_source_v1.h index 0a93c49f6..4244aa09f 100644 --- a/include/wlr/types/wlr_ext_image_capture_source_v1.h +++ b/include/wlr/types/wlr_ext_image_capture_source_v1.h @@ -13,9 +13,12 @@ #include #include +struct wlr_scene; struct wlr_scene_node; struct wlr_allocator; struct wlr_renderer; +struct wlr_output; +struct wlr_output_layout; /** * A screen capture source. @@ -79,6 +82,8 @@ struct wlr_ext_image_capture_source_v1_cursor { */ struct wlr_ext_output_image_capture_source_manager_v1 { struct wl_global *global; + struct wlr_scene *scene; + struct wlr_output_layout *layout; struct { struct wl_listener display_destroy; @@ -122,6 +127,14 @@ struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_from_res struct wlr_ext_output_image_capture_source_manager_v1 *wlr_ext_output_image_capture_source_manager_v1_create( struct wl_display *display, uint32_t version); +void wlr_ext_output_image_capture_source_manager_v1_set_scene( + struct wlr_ext_output_image_capture_source_manager_v1 *manager, + struct wlr_scene *scene); + +void wlr_ext_output_image_capture_source_manager_v1_set_layout( + struct wlr_ext_output_image_capture_source_manager_v1 *manager, + struct wlr_output_layout *layout); + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 * wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(struct wl_display *display, uint32_t version); @@ -133,6 +146,10 @@ struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_create_w struct wlr_scene_node *node, struct wl_event_loop *event_loop, struct wlr_allocator *allocator, struct wlr_renderer *renderer); +struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_create_with_scene_output( + struct wlr_scene *scene, struct wlr_output *reference_output, + struct wlr_output_layout *layout); + /** * Returns the corresponding wlr_output for a image capture source * managed by wlr_ext_output_image_capture_source_manager_v1 diff --git a/types/ext_image_capture_source_v1/output.c b/types/ext_image_capture_source_v1/output.c index 0e3a57823..e7cc14089 100644 --- a/types/ext_image_capture_source_v1/output.c +++ b/types/ext_image_capture_source_v1/output.c @@ -37,12 +37,41 @@ struct wlr_ext_output_image_capture_source_v1 { bool software_cursors_locked; }; +struct scene_output_source_addon { + struct wlr_addon addon; + struct wlr_ext_image_capture_source_v1 *source; + struct wl_listener source_destroy; +}; + struct wlr_ext_output_image_capture_source_v1_frame_event { struct wlr_ext_image_capture_source_v1_frame_event base; struct wlr_buffer *buffer; struct timespec when; }; +static void scene_output_source_addon_handle_source_destroy(struct wl_listener *listener, + void *data) { + struct scene_output_source_addon *addon = + wl_container_of(listener, addon, source_destroy); + (void)data; + wl_list_remove(&addon->source_destroy.link); + wlr_addon_finish(&addon->addon); + free(addon); +} + +static void scene_output_source_addon_destroy(struct wlr_addon *addon) { + struct scene_output_source_addon *scene_addon = + wl_container_of(addon, scene_addon, addon); + wl_list_remove(&scene_addon->source_destroy.link); + wlr_addon_finish(&scene_addon->addon); + free(scene_addon); +} + +static const struct wlr_addon_interface scene_output_source_addon_impl = { + .name = "wlr_ext_output_image_capture_scene_source_v1", + .destroy = scene_output_source_addon_destroy, +}; + static void output_source_start(struct wlr_ext_image_capture_source_v1 *base, bool with_cursors) { struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(base, source, base); @@ -185,30 +214,66 @@ static void output_manager_handle_create_source(struct wl_client *client, return; } - struct wlr_ext_output_image_capture_source_v1 *source; - struct wlr_addon *addon = wlr_addon_find(&output->addons, NULL, &output_addon_impl); - if (addon != NULL) { - source = wl_container_of(addon, source, addon); + struct wlr_ext_output_image_capture_source_manager_v1 *manager = + wl_resource_get_user_data(manager_resource); + struct wlr_ext_image_capture_source_v1 *capture_source = NULL; + + if (manager->scene != NULL) { + struct scene_output_source_addon *scene_addon = NULL; + struct wlr_addon *addon = wlr_addon_find(&output->addons, + NULL, &scene_output_source_addon_impl); + if (addon != NULL) { + scene_addon = wl_container_of(addon, scene_addon, addon); + capture_source = scene_addon->source; + } else { + scene_addon = calloc(1, sizeof(*scene_addon)); + if (scene_addon == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + capture_source = wlr_ext_image_capture_source_v1_create_with_scene_output( + manager->scene, output, manager->layout); + if (capture_source == NULL) { + free(scene_addon); + wl_resource_post_no_memory(manager_resource); + return; + } + + scene_addon->source = capture_source; + scene_addon->source_destroy.notify = scene_output_source_addon_handle_source_destroy; + wl_signal_add(&capture_source->events.destroy, &scene_addon->source_destroy); + wlr_addon_init(&scene_addon->addon, &output->addons, NULL, + &scene_output_source_addon_impl); + } } else { - source = calloc(1, sizeof(*source)); - if (source == NULL) { - wl_resource_post_no_memory(manager_resource); - return; + struct wlr_ext_output_image_capture_source_v1 *source; + struct wlr_addon *addon = wlr_addon_find(&output->addons, NULL, &output_addon_impl); + if (addon != NULL) { + source = wl_container_of(addon, source, addon); + } else { + source = calloc(1, sizeof(*source)); + if (source == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + wlr_ext_image_capture_source_v1_init(&source->base, &output_source_impl); + wlr_addon_init(&source->addon, &output->addons, NULL, &output_addon_impl); + source->output = output; + + source->output_commit.notify = source_handle_output_commit; + wl_signal_add(&output->events.commit, &source->output_commit); + + source_update_buffer_constraints(source); + + output_cursor_source_init(&source->cursor, output); } - wlr_ext_image_capture_source_v1_init(&source->base, &output_source_impl); - wlr_addon_init(&source->addon, &output->addons, NULL, &output_addon_impl); - source->output = output; - - source->output_commit.notify = source_handle_output_commit; - wl_signal_add(&output->events.commit, &source->output_commit); - - source_update_buffer_constraints(source); - - output_cursor_source_init(&source->cursor, output); + capture_source = &source->base; } - if (!wlr_ext_image_capture_source_v1_create_resource(&source->base, client, new_id)) { + if (!wlr_ext_image_capture_source_v1_create_resource(capture_source, client, new_id)) { return; } } @@ -236,6 +301,18 @@ static void output_manager_bind(struct wl_client *client, void *data, wl_resource_set_implementation(resource, &output_manager_impl, manager, NULL); } +void wlr_ext_output_image_capture_source_manager_v1_set_scene( + struct wlr_ext_output_image_capture_source_manager_v1 *manager, + struct wlr_scene *scene) { + manager->scene = scene; +} + +void wlr_ext_output_image_capture_source_manager_v1_set_layout( + struct wlr_ext_output_image_capture_source_manager_v1 *manager, + struct wlr_output_layout *layout) { + manager->layout = layout; +} + static void output_manager_handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_ext_output_image_capture_source_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); diff --git a/types/ext_image_capture_source_v1/scene_output.c b/types/ext_image_capture_source_v1/scene_output.c new file mode 100644 index 000000000..f9fa0454f --- /dev/null +++ b/types/ext_image_capture_source_v1/scene_output.c @@ -0,0 +1,321 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct scene_output_source { + struct wlr_ext_image_capture_source_v1 base; + + struct wlr_scene *scene; + struct wlr_output *ref_output; + struct wlr_output_layout *layout; + + struct wlr_backend *headless_backend; + struct wlr_output *headless; + struct wlr_scene_output *scene_output; + + struct wl_listener headless_frame; + struct wl_listener headless_commit; + struct wl_listener scene_output_destroy; + struct wl_listener ref_output_commit; + struct wl_listener ref_output_destroy; + + size_t num_started; +}; + +struct scene_output_source_frame_event { + struct wlr_ext_image_capture_source_v1_frame_event base; + struct wlr_buffer *buffer; + struct timespec when; +}; + +static void scene_output_source_update_constraints(struct scene_output_source *source) { + struct wlr_output *output = source->ref_output; + + if (!output->enabled) { + return; + } + + if (!wlr_output_configure_primary_swapchain(output, NULL, &output->swapchain)) { + return; + } + + wlr_ext_image_capture_source_v1_set_constraints_from_swapchain(&source->base, + output->swapchain, output->renderer); +} + +static void scene_output_source_handle_ref_output_commit(struct wl_listener *listener, + void *data) { + struct scene_output_source *source = wl_container_of(listener, source, ref_output_commit); + struct wlr_output_event_commit *event = data; + + if (event->state->committed & (WLR_OUTPUT_STATE_MODE | + WLR_OUTPUT_STATE_RENDER_FORMAT | WLR_OUTPUT_STATE_ENABLED)) { + scene_output_source_update_constraints(source); + } +} + +static void scene_output_source_handle_scene_output_destroy(struct wl_listener *listener, + void *data) { + struct scene_output_source *source = wl_container_of(listener, source, scene_output_destroy); + (void)data; + source->scene_output = NULL; + wl_list_remove(&source->scene_output_destroy.link); + wl_list_init(&source->scene_output_destroy.link); +} + +static void scene_output_source_handle_headless_frame(struct wl_listener *listener, + void *data) { + struct scene_output_source *source = wl_container_of(listener, source, headless_frame); + (void)data; + + if (source->scene_output == NULL) { + return; + } + + int width = source->ref_output->width; + int height = source->ref_output->height; + if (width <= 0 || height <= 0) { + return; + } + + pixman_region32_t damage; + pixman_region32_init_rect(&damage, 0, 0, width, height); + pixman_region32_copy(&source->scene_output->pending_commit_damage, &damage); + pixman_region32_fini(&damage); + + struct wlr_scene_output_state_options options = { + .color_transform = NULL, + }; + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + wlr_output_state_set_custom_mode(&state, width, height, source->ref_output->refresh); + wlr_output_state_set_scale(&state, source->ref_output->scale); + wlr_output_state_set_transform(&state, source->ref_output->transform); + wlr_output_state_set_render_format(&state, source->ref_output->render_format); + if (!wlr_scene_output_build_state(source->scene_output, &state, &options)) { + wlr_output_state_finish(&state); + return; + } + + wlr_output_commit_state(source->headless, &state); + wlr_output_state_finish(&state); +} + +static void scene_output_source_handle_headless_commit(struct wl_listener *listener, + void *data) { + struct scene_output_source *source = wl_container_of(listener, source, headless_commit); + struct wlr_output_event_commit *event = data; + + if (!(event->state->committed & WLR_OUTPUT_STATE_BUFFER)) { + return; + } + + struct wlr_buffer *buffer = event->state->buffer; + pixman_region32_t full_damage; + + const pixman_region32_t *damage; + if (event->state->committed & WLR_OUTPUT_STATE_DAMAGE) { + damage = &event->state->damage; + } else { + pixman_region32_init_rect(&full_damage, 0, 0, buffer->width, buffer->height); + damage = &full_damage; + } + + struct scene_output_source_frame_event frame_event = { + .base = { + .damage = damage, + }, + .buffer = buffer, + .when = event->when, + }; + wl_signal_emit_mutable(&source->base.events.frame, &frame_event.base); + + if (damage == &full_damage) { + pixman_region32_fini(&full_damage); + } +} + +static void scene_output_source_start(struct wlr_ext_image_capture_source_v1 *base, + bool with_cursors) { + struct scene_output_source *source = wl_container_of(base, source, base); + (void)with_cursors; + + source->num_started++; + if (source->num_started > 1) { + return; + } + + bool created_backend = false; + if (source->headless_backend == NULL) { + source->headless_backend = wlr_headless_backend_create(source->ref_output->event_loop); + if (source->headless_backend == NULL) { + source->num_started--; + return; + } + created_backend = true; + if (!wlr_backend_start(source->headless_backend)) { + wlr_backend_destroy(source->headless_backend); + source->headless_backend = NULL; + source->num_started--; + return; + } + } + + source->headless = wlr_headless_add_output(source->headless_backend, + source->ref_output->width, source->ref_output->height); + if (source->headless == NULL) { + if (created_backend) { + wlr_backend_destroy(source->headless_backend); + source->headless_backend = NULL; + } + source->num_started--; + return; + } + + wlr_output_init_render(source->headless, + source->ref_output->allocator, source->ref_output->renderer); + + source->scene_output = wlr_scene_output_create(source->scene, source->headless); + if (source->scene_output == NULL) { + wlr_output_destroy(source->headless); + source->headless = NULL; + if (created_backend) { + wlr_backend_destroy(source->headless_backend); + source->headless_backend = NULL; + } + source->num_started--; + return; + } + + if (source->layout != NULL) { + struct wlr_box box; + wlr_output_layout_get_box(source->layout, source->ref_output, &box); + wlr_scene_output_set_position(source->scene_output, box.x, box.y); + } + + source->headless_frame.notify = scene_output_source_handle_headless_frame; + wl_signal_add(&source->headless->events.frame, &source->headless_frame); + + source->headless_commit.notify = scene_output_source_handle_headless_commit; + wl_signal_add(&source->headless->events.commit, &source->headless_commit); + + source->scene_output_destroy.notify = scene_output_source_handle_scene_output_destroy; + wl_signal_add(&source->scene_output->events.destroy, &source->scene_output_destroy); + + scene_output_source_update_constraints(source); + wl_signal_emit_mutable(&source->headless->events.frame, source->headless); +} + +static void scene_output_source_stop(struct wlr_ext_image_capture_source_v1 *base) { + struct scene_output_source *source = wl_container_of(base, source, base); + assert(source->num_started > 0); + + source->num_started--; + if (source->num_started > 0) { + return; + } + + if (source->headless != NULL) { + wl_list_remove(&source->headless_frame.link); + wl_list_remove(&source->headless_commit.link); + if (source->scene_output != NULL) { + wl_list_remove(&source->scene_output_destroy.link); + wlr_scene_output_destroy(source->scene_output); + source->scene_output = NULL; + } + wlr_output_destroy(source->headless); + source->headless = NULL; + } + + if (source->headless_backend != NULL) { + wlr_backend_destroy(source->headless_backend); + source->headless_backend = NULL; + } +} + +static void scene_output_source_request_frame(struct wlr_ext_image_capture_source_v1 *base, + bool schedule_frame) { + struct scene_output_source *source = wl_container_of(base, source, base); + if (schedule_frame && source->headless != NULL) { + wlr_output_schedule_frame(source->headless); + } +} + +static void scene_output_source_copy_frame(struct wlr_ext_image_capture_source_v1 *base, + struct wlr_ext_image_copy_capture_frame_v1 *frame, + struct wlr_ext_image_capture_source_v1_frame_event *base_event) { + struct scene_output_source *source = wl_container_of(base, source, base); + struct scene_output_source_frame_event *event = wl_container_of(base_event, event, base); + + if (wlr_ext_image_copy_capture_frame_v1_copy_buffer(frame, + event->buffer, source->ref_output->renderer)) { + wlr_ext_image_copy_capture_frame_v1_ready(frame, + source->ref_output->transform, &event->when); + } +} + +static const struct wlr_ext_image_capture_source_v1_interface scene_output_source_impl = { + .start = scene_output_source_start, + .stop = scene_output_source_stop, + .request_frame = scene_output_source_request_frame, + .copy_frame = scene_output_source_copy_frame, +}; + +static void scene_output_source_destroy(struct scene_output_source *source) { + if (source->num_started > 0) { + scene_output_source_stop(&source->base); + } + + wl_list_remove(&source->ref_output_commit.link); + wl_list_remove(&source->ref_output_destroy.link); + + wlr_ext_image_capture_source_v1_finish(&source->base); + free(source); +} + +static void scene_output_source_handle_ref_output_destroy(struct wl_listener *listener, + void *data) { + struct scene_output_source *source = wl_container_of(listener, source, ref_output_destroy); + (void)data; + scene_output_source_destroy(source); +} + +struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_create_with_scene_output( + struct wlr_scene *scene, struct wlr_output *reference_output, + struct wlr_output_layout *layout) { + struct scene_output_source *source = calloc(1, sizeof(*source)); + if (source == NULL) { + return NULL; + } + + *source = (struct scene_output_source){ + .scene = scene, + .ref_output = reference_output, + .layout = layout, + }; + + wlr_ext_image_capture_source_v1_init(&source->base, &scene_output_source_impl); + + wl_list_init(&source->headless_frame.link); + wl_list_init(&source->headless_commit.link); + wl_list_init(&source->scene_output_destroy.link); + + source->ref_output_commit.notify = scene_output_source_handle_ref_output_commit; + wl_signal_add(&reference_output->events.commit, &source->ref_output_commit); + + source->ref_output_destroy.notify = scene_output_source_handle_ref_output_destroy; + wl_signal_add(&reference_output->events.destroy, &source->ref_output_destroy); + + scene_output_source_update_constraints(source); + + return &source->base; +} diff --git a/types/meson.build b/types/meson.build index 0513b8fc8..b70fa72ea 100644 --- a/types/meson.build +++ b/types/meson.build @@ -7,6 +7,7 @@ wlr_files += files( 'ext_image_capture_source_v1/output.c', 'ext_image_capture_source_v1/foreign_toplevel.c', 'ext_image_capture_source_v1/scene.c', + 'ext_image_capture_source_v1/scene_output.c', 'output/cursor.c', 'output/output.c', 'output/render.c',