diff --git a/cmake/compile_definitions/linux.cmake b/cmake/compile_definitions/linux.cmake index 485e419adb5..a122e149007 100644 --- a/cmake/compile_definitions/linux.cmake +++ b/cmake/compile_definitions/linux.cmake @@ -213,8 +213,11 @@ if(WAYLAND_FOUND) pkg_check_modules(WAYLAND_PROTOCOLS wayland-protocols REQUIRED) endif() + GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "stable/linux-dmabuf" linux-dmabuf-v1) + GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "staging/ext-image-copy-capture" ext-image-copy-capture-v1) + GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "staging/ext-image-capture-source" ext-image-capture-source-v1) + GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "staging/ext-foreign-toplevel-list" ext-foreign-toplevel-list-v1) GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "unstable/xdg-output" xdg-output-unstable-v1) - GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "unstable/linux-dmabuf" linux-dmabuf-unstable-v1) GEN_WAYLAND("${CMAKE_SOURCE_DIR}/third-party/wlr-protocols" "unstable" wlr-screencopy-unstable-v1) include_directories( diff --git a/src/platform/linux/wayland.cpp b/src/platform/linux/wayland.cpp index 47055920b02..a5b2c990242 100644 --- a/src/platform/linux/wayland.cpp +++ b/src/platform/linux/wayland.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -48,8 +49,19 @@ namespace wl { // Define buffer params listener static const struct zwp_linux_buffer_params_v1_listener params_listener = { - .created = dmabuf_t::buffer_params_created, - .failed = dmabuf_t::buffer_params_failed + .created = CLASS_CALL(dmabuf_t, buffer_params_created), + .failed = CLASS_CALL(dmabuf_t, buffer_params_failed), + }; + + // Define buffer feedback listener + static const struct zwp_linux_dmabuf_feedback_v1_listener feedback_listener = { + .done = CLASS_CALL(dmabuf_t, buffer_feedback_done), + .format_table = CLASS_CALL(dmabuf_t, buffer_feedback_format_table), + .main_device = CLASS_CALL(dmabuf_t, buffer_feedback_main_device), + .tranche_done = CLASS_CALL(dmabuf_t, buffer_feedback_tranche_done), + .tranche_target_device = CLASS_CALL(dmabuf_t, buffer_feedback_tranche_target_device), + .tranche_formats = CLASS_CALL(dmabuf_t, buffer_feedback_tranche_formats), + .tranche_flags = CLASS_CALL(dmabuf_t, buffer_feedback_tranche_flags), }; int display_t::init(const char *display_name) { @@ -184,13 +196,11 @@ namespace wl { screencopy_manager {nullptr}, dmabuf_interface {nullptr}, output_manager {nullptr}, + icc_manager {nullptr}, + icc_source_manager {nullptr}, listener { &CLASS_CALL(interface_t, add_interface), &CLASS_CALL(interface_t, del_interface) - }, - dmabuf_listener { - &CLASS_CALL(interface_t, dmabuf_format), - &CLASS_CALL(interface_t, dmabuf_modifier) } { } @@ -198,14 +208,6 @@ namespace wl { wl_registry_add_listener(registry, &listener, this); } - void interface_t::dmabuf_format(zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format) { - } - - void interface_t::dmabuf_modifier(zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { - uint64_t modifier = ((uint64_t) modifier_hi << 32) | modifier_lo; - supported_modifiers[format].push_back(modifier); - } - void interface_t::add_interface( wl_registry *registry, std::uint32_t id, @@ -214,6 +216,8 @@ namespace wl { ) { BOOST_LOG(debug) << "[wayland] Available interface: "sv << interface << '(' << id << ") version "sv << version; + const auto *wayland_backend = std::getenv("SUNSHINE_WAYLAND_BACKEND"); + if (!std::strcmp(interface, wl_output_interface.name)) { BOOST_LOG(info) << "[wayland] Found interface: "sv << interface << '(' << id << ") version "sv << version; monitors.emplace_back( @@ -226,17 +230,26 @@ namespace wl { output_manager = (zxdg_output_manager_v1 *) wl_registry_bind(registry, id, &zxdg_output_manager_v1_interface, version); this->interface[XDG_OUTPUT] = true; - } else if (!std::strcmp(interface, zwlr_screencopy_manager_v1_interface.name)) { - BOOST_LOG(info) << "[wayland] Found interface: "sv << interface << '(' << id << ") version "sv << version; + } else if (!std::strcmp(interface, zwlr_screencopy_manager_v1_interface.name) && (!wayland_backend || !strcmp(wayland_backend, "screencopy"))) { + BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version; screencopy_manager = (zwlr_screencopy_manager_v1 *) wl_registry_bind(registry, id, &zwlr_screencopy_manager_v1_interface, version); - this->interface[WLR_EXPORT_DMABUF] = true; + this->interface[WLR_SCREENCOPY] = true; } else if (!std::strcmp(interface, zwp_linux_dmabuf_v1_interface.name)) { BOOST_LOG(info) << "[wayland] Found interface: "sv << interface << '(' << id << ") version "sv << version; - dmabuf_interface = (zwp_linux_dmabuf_v1 *) wl_registry_bind(registry, id, &zwp_linux_dmabuf_v1_interface, std::min(version, 3u)); - zwp_linux_dmabuf_v1_add_listener(dmabuf_interface, &dmabuf_listener, this); + dmabuf_interface = (zwp_linux_dmabuf_v1 *) wl_registry_bind(registry, id, &zwp_linux_dmabuf_v1_interface, version); this->interface[LINUX_DMABUF] = true; + } else if (!std::strcmp(interface, ext_image_copy_capture_manager_v1_interface.name) && (!wayland_backend || !strcmp(wayland_backend, "icc"))) { + BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version; + icc_manager = (ext_image_copy_capture_manager_v1 *) wl_registry_bind(registry, id, &ext_image_copy_capture_manager_v1_interface, version); + + this->interface[EXT_IMAGE_COPY_CAPTURE] = true; + } else if (!std::strcmp(interface, ext_output_image_capture_source_manager_v1_interface.name) && (!wayland_backend || !strcmp(wayland_backend, "icc"))) { + BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version; + icc_source_manager = (ext_output_image_capture_source_manager_v1 *) wl_registry_bind(registry, id, &ext_output_image_capture_source_manager_v1_interface, version); + + this->interface[EXT_IMAGE_CAPTURE_SOURCE] = true; } } @@ -250,11 +263,43 @@ namespace wl { return true; } - auto render_path = platf::resolve_render_device(); - int drm_fd = open(render_path.c_str(), O_RDWR); + // We don't have a drm device from the protocol for some reason + // This should never happen, but this code was already here so it's a fallback now + + auto node = config::video.adapter_name; + int drm_fd = -1; + if (!node.empty()) { + // Prefer adapter_name from config + drm_fd = open(node.c_str(), O_RDWR); + } + if (drm_fd < 0) { - BOOST_LOG(error) << "[wayland] Failed to open DRM render node: "sv << render_path; + // Find any render node and hope that works + drmDevice *devices[16]; + int n = drmGetDevices2(0, devices, 16); + if (n <= 0) { + BOOST_LOG(error) << "No DRM devices found"sv; + return false; + } + + int drm_fd = -1; + for (int i = 0; i < n; i++) { + if (devices[i]->available_nodes & (1 << DRM_NODE_RENDER)) { + drm_fd = open(devices[i]->nodes[DRM_NODE_RENDER], O_RDWR); + if (drm_fd >= 0) { + node = std::string {devices[i]->nodes[DRM_NODE_RENDER]}; + break; + } + } + } + drmFreeDevices(devices, n); + } + + if (drm_fd < 0) { + BOOST_LOG(error) << "Failed to open DRM render node: " << node; return false; + } else { + BOOST_LOG(info) << "Using DRM render node: " << node; } gbm_device = gbm_create_device(drm_fd); @@ -296,33 +341,48 @@ namespace wl { } // Start capture - void dmabuf_t::listen( + void dmabuf_t::screencopy_create( zwlr_screencopy_manager_v1 *screencopy_manager, zwp_linux_dmabuf_v1 *dmabuf_interface, - const std::map> *supported_modifiers, - wl_output *output, - bool blend_cursor + wl_output *output ) { this->dmabuf_interface = dmabuf_interface; - this->supported_modifiers = supported_modifiers; // Reset state shm_info.supported = false; dmabuf_info.supported = false; + screencopy_session = {}; - // Create new frame - auto frame = zwlr_screencopy_manager_v1_capture_output( - screencopy_manager, - blend_cursor ? 1 : 0, - output - ); + screencopy_session.manager = screencopy_manager; + screencopy_session.output = output; - // Store frame data pointer for callbacks - zwlr_screencopy_frame_v1_set_user_data(frame, this); + // Get feedback for correct drm device to use + auto feedback = zwp_linux_dmabuf_v1_get_default_feedback(dmabuf_interface); + zwp_linux_dmabuf_feedback_v1_set_user_data(feedback, this); + zwp_linux_dmabuf_feedback_v1_add_listener(feedback, &feedback_listener, this); + } - // Add listener - zwlr_screencopy_frame_v1_add_listener(frame, &listener, this); + void dmabuf_t::screencopy_capture(bool blend_cursor) { + if (!screencopy_session.done) { + status = REINIT; + return; + } else if (screencopy_session.manager && screencopy_session.output && screencopy_session.done && !screencopy_session.frame) { + // Create new frame + auto frame = zwlr_screencopy_manager_v1_capture_output( + screencopy_session.manager, + blend_cursor ? 1 : 0, + screencopy_session.output + ); - status = WAITING; + // Store frame data pointer for callbacks + zwlr_screencopy_frame_v1_set_user_data(frame, this); + + // Add listener + zwlr_screencopy_frame_v1_add_listener(frame, &listener, this); + + screencopy_session.frame = frame; + + status = WAITING; + } } dmabuf_t::~dmabuf_t() { @@ -361,6 +421,11 @@ namespace wl { std::uint32_t width, std::uint32_t height ) { + if (dmabuf_info.format != format) { + char fourcc[5] {0}; + memcpy(&fourcc, &format, 4); + BOOST_LOG(info) << "Screencopy DMA-BUF format: " << std::hex << format << " = " << fourcc; + } dmabuf_info.supported = true; dmabuf_info.format = format; dmabuf_info.width = width; @@ -383,9 +448,9 @@ namespace wl { } // Create GBM buffer - if (supported_modifiers) { - auto it = supported_modifiers->find(dmabuf_info.format); - if (it != supported_modifiers->end() && !it->second.empty()) { + if (!screencopy_session.supported_modifiers.empty()) { + auto it = screencopy_session.supported_modifiers.find(dmabuf_info.format); + if (it != screencopy_session.supported_modifiers.end() && !it->second.empty()) { current_bo = gbm_bo_create_with_modifiers(gbm_device, dmabuf_info.width, dmabuf_info.height, dmabuf_info.format, it->second.data(), it->second.size()); } } @@ -425,9 +490,8 @@ namespace wl { // Create linux-dmabuf buffer auto params = zwp_linux_dmabuf_v1_create_params(dmabuf_interface); zwp_linux_buffer_params_v1_add(params, fd, 0, 0, stride, modifier >> 32, modifier & 0xffffffff); - - // Add listener for buffer creation - zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, frame); + zwp_linux_buffer_params_v1_set_user_data(params, this); + zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, this); // Create Wayland buffer (async - callback will handle copy) zwp_linux_buffer_params_v1_create(params, dmabuf_info.width, dmabuf_info.height, dmabuf_info.format, 0); @@ -460,37 +524,117 @@ namespace wl { // Buffer params created callback void dmabuf_t::buffer_params_created( - void *data, - struct zwp_linux_buffer_params_v1 *params, + zwp_linux_buffer_params_v1 *params, struct wl_buffer *buffer ) { - auto frame = static_cast(data); - auto self = static_cast(zwlr_screencopy_frame_v1_get_user_data(frame)); - // Store for cleanup - self->current_wl_buffer = buffer; + current_wl_buffer = buffer; // Start the actual copy zwp_linux_buffer_params_v1_destroy(params); - zwlr_screencopy_frame_v1_copy(frame, buffer); + zwlr_screencopy_frame_v1_copy(screencopy_session.frame, buffer); } // Buffer params failed callback void dmabuf_t::buffer_params_failed( - void *data, - struct zwp_linux_buffer_params_v1 *params + zwp_linux_buffer_params_v1 *params ) { - auto frame = static_cast(data); - auto self = static_cast(zwlr_screencopy_frame_v1_get_user_data(frame)); - - BOOST_LOG(error) << "[wayland] Failed to create buffer from params"sv; - self->cleanup_gbm(); + BOOST_LOG(error) << "Failed to create buffer from params"sv; + cleanup_gbm(); zwp_linux_buffer_params_v1_destroy(params); - zwlr_screencopy_frame_v1_destroy(frame); - self->status = REINIT; + zwlr_screencopy_frame_v1_destroy(screencopy_session.frame); + status = REINIT; + } + + void dmabuf_t::buffer_feedback_done(zwp_linux_dmabuf_feedback_v1 *feedback) { + screencopy_session.done = true; + status = WAITING; } + struct buffer_feedback_format_table_entry { + std::uint32_t format; + std::uint32_t padding; + std::uint64_t modifier; + }; + + void dmabuf_t::buffer_feedback_format_table(zwp_linux_dmabuf_feedback_v1 *feedback, int fd, int size) { + if (fd < 0 || size == 0) { + return; + } + + void *map_data = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (map_data == MAP_FAILED) { + return; + } + + auto *entries = static_cast(map_data); + std::size_t num_entries = size / sizeof(buffer_feedback_format_table_entry); + + char fourcc[5] {0}; + + for (std::size_t i = 0; i < num_entries; ++i) { + std::uint32_t format = entries[i].format; + std::uint64_t modifier = entries[i].modifier; + memcpy(&fourcc, &format, 4); + BOOST_LOG(debug) << "Screencopy DMA-BUF format: " << std::hex << format << " = " << fourcc << ", modifier: " << modifier; + screencopy_session.supported_modifiers[format].push_back(modifier); + } + + munmap(map_data, size); + } + + void dmabuf_t::buffer_feedback_main_device(zwp_linux_dmabuf_feedback_v1 *feedback, struct wl_array *device) { + if (device->size != sizeof(dev_t)) { + BOOST_LOG(error) << "Screencopy DMA-BUF device size mismatch: " << device->size << " != " << sizeof(dev_t); + status = REINIT; + return; + } + + dev_t device_id; + memcpy(&device_id, device->data, sizeof(dev_t)); + + drmDevice *drm_device {nullptr}; + if (drmGetDeviceFromDevId(device_id, 0, &drm_device) != 0) { + BOOST_LOG(error) << "Screencopy failed to open DRM device"; + status = REINIT; + return; + } + + char *node {nullptr}; + if (drm_device->available_nodes & (1 << DRM_NODE_RENDER)) { + node = drm_device->nodes[DRM_NODE_RENDER]; + } else if (drm_device->available_nodes & (1 << DRM_NODE_PRIMARY)) { + node = drm_device->nodes[DRM_NODE_PRIMARY]; + } else { + BOOST_LOG(error) << "Screencopy failed to find DRM node"; + status = REINIT; + return; + } + + int drm_fd = open(node, O_RDWR); + if (drm_fd < 0) { + BOOST_LOG(error) << "Screencopy failed to open DRM render node"; + status = REINIT; + return; + } + + gbm_device = gbm_create_device(drm_fd); + if (!gbm_device) { + close(drm_fd); + BOOST_LOG(error) << "Screencopy failed to create GBM device"; + status = REINIT; + return; + } + + BOOST_LOG(info) << "Screencopy DMA-BUF device: " << node; + } + + void dmabuf_t::buffer_feedback_tranche_done(zwp_linux_dmabuf_feedback_v1 *feedback) {}; + void dmabuf_t::buffer_feedback_tranche_target_device(zwp_linux_dmabuf_feedback_v1 *feedback, struct wl_array *device) {}; + void dmabuf_t::buffer_feedback_tranche_formats(zwp_linux_dmabuf_feedback_v1 *feedback, struct wl_array *formats) {}; + void dmabuf_t::buffer_feedback_tranche_flags(zwp_linux_dmabuf_feedback_v1 *feedback, int flags) {}; + // Ready callback void dmabuf_t::ready( zwlr_screencopy_frame_v1 *frame, @@ -517,6 +661,7 @@ namespace wl { cleanup_gbm(); zwlr_screencopy_frame_v1_destroy(frame); + screencopy_session.frame = nullptr; status = READY; } @@ -601,6 +746,321 @@ namespace wl { return !validated; } + // ext-image-copy-capture implementation starts here + + static const struct zwp_linux_buffer_params_v1_listener icc_params_listener = { + .created = CLASS_CALL(dmabuf_t, icc_buffer_params_created), + .failed = CLASS_CALL(dmabuf_t, icc_buffer_params_failed), + }; + + static const struct ext_image_copy_capture_session_v1_listener icc_session_listener = { + .buffer_size = CLASS_CALL(dmabuf_t, icc_session_buffer_size), + .shm_format = CLASS_CALL(dmabuf_t, icc_session_shm_format), + .dmabuf_device = CLASS_CALL(dmabuf_t, icc_session_dmabuf_device), + .dmabuf_format = CLASS_CALL(dmabuf_t, icc_session_dmabuf_format), + .done = CLASS_CALL(dmabuf_t, icc_session_done), + .stopped = CLASS_CALL(dmabuf_t, icc_session_stopped), + }; + + static const struct ext_image_copy_capture_frame_v1_listener icc_frame_listener = { + .transform = CLASS_CALL(dmabuf_t, icc_frame_transform), + .damage = CLASS_CALL(dmabuf_t, icc_frame_damage), + .presentation_time = CLASS_CALL(dmabuf_t, icc_frame_presentation_time), + .ready = CLASS_CALL(dmabuf_t, icc_frame_ready), + .failed = CLASS_CALL(dmabuf_t, icc_frame_failed), + }; + + void dmabuf_t::icc_create( + ext_image_copy_capture_manager_v1 *manager, + ext_output_image_capture_source_manager_v1 *source_manager, + zwp_linux_dmabuf_v1 *dmabuf_interface, + wl_output *output, + bool blend_cursor + ) { + this->dmabuf_interface = dmabuf_interface; + icc_cleanup(); + icc_session.cursor = blend_cursor; + + auto icc_source = ext_output_image_capture_source_manager_v1_create_source(source_manager, output); + auto session = ext_image_copy_capture_manager_v1_create_session(manager, icc_source, blend_cursor ? EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_OPTIONS_PAINT_CURSORS : 0); + ext_image_copy_capture_session_v1_set_user_data(session, this); + ext_image_copy_capture_session_v1_add_listener(session, &icc_session_listener, this); + icc_session.session = session; + status = WAITING; + } + + void dmabuf_t::icc_capture(bool blend_cursor) { + if (!icc_session.session || blend_cursor != icc_session.cursor) { + status = REINIT; + return; + } else if (icc_session.done && !icc_session.frame) { + auto frame = ext_image_copy_capture_session_v1_create_frame(icc_session.session); + icc_session.frame = frame; + ext_image_copy_capture_frame_v1_set_user_data(frame, this); + ext_image_copy_capture_frame_v1_add_listener(frame, &icc_frame_listener, this); + icc_create_and_copy_dmabuf(frame); + status = WAITING; + } + } + + void dmabuf_t::icc_create_and_copy_dmabuf(ext_image_copy_capture_frame_v1 *frame) { + if (!init_gbm()) { + BOOST_LOG(error) << "Failed to initialize GBM"sv; + ext_image_copy_capture_frame_v1_destroy(frame); + status = REINIT; + return; + } + + if (icc_session.width == 0 || icc_session.height == 0 || icc_session.format == 0) { + BOOST_LOG(error) << "Invalid capture session parameters"sv; + ext_image_copy_capture_frame_v1_destroy(frame); + status = REINIT; + return; + } + + if (!icc_session.supported_modifiers.empty()) { + auto it = icc_session.supported_modifiers.find(icc_session.format); + if (it != icc_session.supported_modifiers.end() && !it->second.empty()) { + current_bo = gbm_bo_create_with_modifiers(gbm_device, icc_session.width, icc_session.height, icc_session.format, it->second.data(), it->second.size()); + } + } + + if (!current_bo) { + current_bo = gbm_bo_create(gbm_device, icc_session.width, icc_session.height, icc_session.format, GBM_BO_USE_RENDERING); + } + + if (!current_bo) { + BOOST_LOG(error) << "Failed to create GBM buffer"sv; + ext_image_copy_capture_frame_v1_destroy(frame); + status = REINIT; + return; + } + + int fd = gbm_bo_get_fd(current_bo); + if (fd < 0) { + BOOST_LOG(error) << "Failed to get buffer FD"sv; + gbm_bo_destroy(current_bo); + current_bo = nullptr; + ext_image_copy_capture_frame_v1_destroy(frame); + status = REINIT; + return; + } + + uint32_t stride = gbm_bo_get_stride(current_bo); + auto next_frame = get_next_frame(); + next_frame->sd.fds[0] = fd; + next_frame->sd.pitches[0] = stride; + next_frame->sd.offsets[0] = 0; + // next_frame->sd.modifier = icc_session.num_modifiers > 0 ? icc_session.modifiers[icc_session.modifier] : DRM_FORMAT_MOD_LINEAR; + next_frame->sd.modifier = gbm_bo_get_modifier(current_bo); + next_frame->sd.fourcc = icc_session.format; + next_frame->sd.width = icc_session.width; + next_frame->sd.height = icc_session.height; + + auto params = zwp_linux_dmabuf_v1_create_params(dmabuf_interface); + zwp_linux_buffer_params_v1_add(params, fd, 0, 0, stride, next_frame->sd.modifier >> 32, next_frame->sd.modifier & 0xffffffff); + zwp_linux_buffer_params_v1_set_user_data(params, this); + zwp_linux_buffer_params_v1_add_listener(params, &icc_params_listener, this); + zwp_linux_buffer_params_v1_create(params, icc_session.width, icc_session.height, icc_session.format, 0); + } + + void dmabuf_t::icc_buffer_params_created( + zwp_linux_buffer_params_v1 *params, + struct wl_buffer *buffer + ) { + current_wl_buffer = buffer; + + zwp_linux_buffer_params_v1_destroy(params); + if (icc_session.frame) { + ext_image_copy_capture_frame_v1_attach_buffer(icc_session.frame, buffer); + ext_image_copy_capture_frame_v1_damage_buffer(icc_session.frame, 0, 0, icc_session.width, icc_session.height); + ext_image_copy_capture_frame_v1_capture(icc_session.frame); + } + } + + void dmabuf_t::icc_buffer_params_failed( + zwp_linux_buffer_params_v1 *params + ) { + BOOST_LOG(error) << "Failed to create buffer for ICC"sv; + + cleanup_gbm(); + status = REINIT; + + zwp_linux_buffer_params_v1_destroy(params); + if (icc_session.frame) { + ext_image_copy_capture_frame_v1_destroy(icc_session.frame); + icc_session.frame = nullptr; + } + } + + void dmabuf_t::icc_session_buffer_size(ext_image_copy_capture_session_v1 *session, std::uint32_t width, std::uint32_t height) { + icc_session.width = width; + icc_session.height = height; + BOOST_LOG(info) << "ICC session buffer size: " << width << 'x' << height; + } + + void dmabuf_t::icc_session_shm_format(ext_image_copy_capture_session_v1 *session, std::uint32_t format) { + shm_info.supported = true; + shm_info.format = format; + BOOST_LOG(debug) << "ICC session SHM format: " << format; + } + + void dmabuf_t::icc_session_dmabuf_device(ext_image_copy_capture_session_v1 *session, struct wl_array *device) { + if (device->size != sizeof(dev_t)) { + BOOST_LOG(error) << "ICC session DMA-BUF device size mismatch: " << device->size << " != " << sizeof(dev_t); + status = REINIT; + return; + } + + dev_t device_id; + memcpy(&device_id, device->data, sizeof(dev_t)); + + drmDevice *drm_device {nullptr}; + if (drmGetDeviceFromDevId(device_id, 0, &drm_device) != 0) { + BOOST_LOG(info) << "ICC session failed to open DRM device"; + status = REINIT; + return; + } + + char *node {nullptr}; + if (drm_device->available_nodes & (1 << DRM_NODE_RENDER)) { + node = drm_device->nodes[DRM_NODE_RENDER]; + } else if (drm_device->available_nodes & (1 << DRM_NODE_PRIMARY)) { + node = drm_device->nodes[DRM_NODE_PRIMARY]; + } else { + BOOST_LOG(info) << "ICC session failed to find DRM node"; + status = REINIT; + return; + } + + int drm_fd = open(node, O_RDWR); + if (drm_fd < 0) { + BOOST_LOG(error) << "ICC session failed to open DRM render node"; + status = REINIT; + return; + } + + gbm_device = gbm_create_device(drm_fd); + if (!gbm_device) { + close(drm_fd); + BOOST_LOG(error) << "ICC session failed to create GBM device"; + status = REINIT; + return; + } + + BOOST_LOG(info) << "ICC session DMA-BUF device: " << node; + } + + void dmabuf_t::icc_session_dmabuf_format(ext_image_copy_capture_session_v1 *session, std::uint32_t format, struct wl_array *modifiers) { + char fourcc[5] {0}; + memcpy(&fourcc, &format, 4); + BOOST_LOG(info) << "ICC session DMA-BUF format: " << std::hex << format << " = " << fourcc; + icc_session.dmabuf_supported = true; + if (icc_session.modifiers != nullptr) { + free(icc_session.modifiers); + icc_session.modifiers = nullptr; + } + icc_session.num_modifiers = modifiers->size / sizeof(uint64_t); + icc_session.modifiers = (uint64_t *) calloc(icc_session.num_modifiers, sizeof(uint64_t)); + icc_session.modifier = 0; + if (icc_session.modifiers != nullptr) { + memcpy(icc_session.modifiers, modifiers->data, modifiers->size); + for (size_t i = 0; i < icc_session.num_modifiers; ++i) { + BOOST_LOG(info) << " Modifier " << std::setw(2) << i << ": " << std::hex << icc_session.modifiers[i]; + icc_session.supported_modifiers[format].push_back(icc_session.modifiers[i]); + } + } + if (!icc_session.format) { + icc_session.format = format; + } + } + + void dmabuf_t::icc_session_done(ext_image_copy_capture_session_v1 *session) { + BOOST_LOG(debug) << "ICC session constraints done"; + + if (!icc_session.session) { + BOOST_LOG(error) << "No ICC session"sv; + status = REINIT; + return; + } + + if (icc_session.width == 0 || icc_session.height == 0) { + BOOST_LOG(error) << "Invalid ICC buffer size"sv; + status = REINIT; + return; + } + + if (!icc_session.dmabuf_supported) { + BOOST_LOG(error) << "ICC session does not support dmabuf"sv; + status = REINIT; + return; + } + + icc_session.done = true; + } + + void dmabuf_t::icc_cleanup() { + if (icc_session.modifiers) { + free(icc_session.modifiers); + } + icc_session = {}; + } + + void dmabuf_t::icc_session_stopped(ext_image_copy_capture_session_v1 *session) { + BOOST_LOG(warning) << "ICC session stopped"sv; + ext_image_copy_capture_session_v1_destroy(session); + icc_cleanup(); + status = REINIT; + } + + void dmabuf_t::icc_frame_transform(ext_image_copy_capture_frame_v1 *frame, std::uint32_t transform) { + // TODO: this could probably do something + BOOST_LOG(debug) << "ICC transform: " << transform; + } + + void dmabuf_t::icc_frame_damage(ext_image_copy_capture_frame_v1 *frame, std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) { + BOOST_LOG(debug) << "ICC damage: " << x << ',' << y << ' ' << width << 'x' << height; + } + + void dmabuf_t::icc_frame_presentation_time(ext_image_copy_capture_frame_v1 *frame, std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec) { + uint64_t timestamp = ((uint64_t) tv_sec_hi << 32) | tv_sec_lo; + BOOST_LOG(debug) << "ICC presentation time: " << timestamp << '.' << tv_nsec; + } + + void dmabuf_t::icc_frame_ready(ext_image_copy_capture_frame_v1 *frame) { + BOOST_LOG(debug) << "ICC frame ready"sv; + current_frame->destroy(); + current_frame = get_next_frame(); + if (current_wl_buffer) { + wl_buffer_destroy(current_wl_buffer); + current_wl_buffer = nullptr; + } + cleanup_gbm(); + ext_image_copy_capture_frame_v1_destroy(frame); + icc_session.frame = nullptr; + status = READY; + } + + void dmabuf_t::icc_frame_failed(ext_image_copy_capture_frame_v1 *frame, std::uint32_t reason) { + if (reason == EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED) { + BOOST_LOG(error) << "ICC frame failed: Session has stopped or too many failures, restarting"; + cleanup_gbm(); + auto next_frame = get_next_frame(); + next_frame->destroy(); + ext_image_copy_capture_frame_v1_destroy(frame); + icc_cleanup(); + status = REINIT; + return; + } else if (reason == EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS) { + BOOST_LOG(error) << "ICC frame failed: Buffer constraints"; + } else { + BOOST_LOG(error) << "ICC frame failed: Unknown reason"; + } + ext_image_copy_capture_frame_v1_destroy(frame); + icc_session.frame = nullptr; + icc_capture(icc_session.cursor); + } + } // namespace wl #pragma GCC diagnostic pop diff --git a/src/platform/linux/wayland.h b/src/platform/linux/wayland.h index 137040021b8..985f1d75e12 100644 --- a/src/platform/linux/wayland.h +++ b/src/platform/linux/wayland.h @@ -11,7 +11,9 @@ #include #ifdef SUNSHINE_BUILD_WAYLAND - #include + #include + #include + #include #include #include #endif @@ -68,40 +70,18 @@ namespace wl { dmabuf_t &operator=(const dmabuf_t &) = delete; dmabuf_t &operator=(dmabuf_t &&) = delete; - /** - * @brief Start a screencopy request and attach the DMA-BUF listener callbacks. - * - * @param screencopy_manager Compositor screencopy manager used to request frames. - * @param dmabuf_interface Compositor DMA-BUF interface used to allocate buffers. - * @param supported_modifiers DMA-BUF format modifiers supported by the compositor. - * @param output Wayland output to capture. - * @param blend_cursor Whether the compositor should include the cursor in the frame. - */ - void listen(zwlr_screencopy_manager_v1 *screencopy_manager, zwp_linux_dmabuf_v1 *dmabuf_interface, const std::map> *supported_modifiers, wl_output *output, bool blend_cursor = false); - /** - * @brief Store the Wayland buffer created for a DMA-BUF parameter request. - * - * @param data Pointer to the `dmabuf_t` instance associated with the callback. - * @param params DMA-BUF parameter object that completed buffer creation. - * @param wl_buffer Wayland buffer created from the DMA-BUF planes. - */ - static void buffer_params_created(void *data, struct zwp_linux_buffer_params_v1 *params, struct wl_buffer *wl_buffer); - /** - * @brief Mark DMA-BUF buffer creation as failed. - * - * @param data Pointer to the `dmabuf_t` instance associated with the callback. - * @param params DMA-BUF parameter object that failed buffer creation. - */ - static void buffer_params_failed(void *data, struct zwp_linux_buffer_params_v1 *params); - /** - * @brief Record DMA-BUF buffer parameters from the compositor. - * - * @param frame Screencopy frame that advertised shared-memory fallback data. - * @param format DRM pixel format for the shared-memory buffer. - * @param width Frame width in pixels. - * @param height Frame height in pixels. - * @param stride Number of bytes per row in the buffer. - */ + // wlr-screencopy based capture + void screencopy_create(zwlr_screencopy_manager_v1 *screencopy_manager, zwp_linux_dmabuf_v1 *dmabuf_interface, wl_output *output); + void screencopy_capture(bool blend_cursor = false); + void buffer_params_created(zwp_linux_buffer_params_v1 *params, struct wl_buffer *wl_buffer); + void buffer_params_failed(zwp_linux_buffer_params_v1 *params); + void buffer_feedback_done(zwp_linux_dmabuf_feedback_v1 *feedback); + void buffer_feedback_format_table(zwp_linux_dmabuf_feedback_v1 *feedback, int fd, int size); + void buffer_feedback_main_device(zwp_linux_dmabuf_feedback_v1 *feedback, struct wl_array *device); + void buffer_feedback_tranche_done(zwp_linux_dmabuf_feedback_v1 *feedback); + void buffer_feedback_tranche_target_device(zwp_linux_dmabuf_feedback_v1 *feedback, struct wl_array *device); + void buffer_feedback_tranche_formats(zwp_linux_dmabuf_feedback_v1 *feedback, struct wl_array *formats); + void buffer_feedback_tranche_flags(zwp_linux_dmabuf_feedback_v1 *feedback, int flags); void buffer(zwlr_screencopy_frame_v1 *frame, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t stride); /** * @brief Record Linux DMA-BUF parameters advertised for the frame. @@ -151,11 +131,24 @@ namespace wl { */ void failed(zwlr_screencopy_frame_v1 *frame); - /** - * @brief Select the inactive frame slot for the next screencopy request. - * - * @return Inactive frame buffer that can receive the next capture. - */ + // ext-image-copy-capture based capture + void icc_create(ext_image_copy_capture_manager_v1 *manager, ext_output_image_capture_source_manager_v1 *source_manager, zwp_linux_dmabuf_v1 *dmabuf_interface, wl_output *output, bool blend_cursor = false); + void icc_capture(bool blend_cursor = false); + void icc_cleanup(); + void icc_buffer_params_created(zwp_linux_buffer_params_v1 *params, struct wl_buffer *buffer); + void icc_buffer_params_failed(zwp_linux_buffer_params_v1 *params); + void icc_session_buffer_size(ext_image_copy_capture_session_v1 *session, std::uint32_t width, std::uint32_t height); + void icc_session_shm_format(ext_image_copy_capture_session_v1 *session, std::uint32_t format); + void icc_session_dmabuf_device(ext_image_copy_capture_session_v1 *session, struct wl_array *device); + void icc_session_dmabuf_format(ext_image_copy_capture_session_v1 *session, std::uint32_t format, struct wl_array *modifiers); + void icc_session_done(ext_image_copy_capture_session_v1 *session); + void icc_session_stopped(ext_image_copy_capture_session_v1 *session); + void icc_frame_transform(ext_image_copy_capture_frame_v1 *frame, std::uint32_t transform); + void icc_frame_damage(ext_image_copy_capture_frame_v1 *frame, std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height); + void icc_frame_presentation_time(ext_image_copy_capture_frame_v1 *frame, std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec); + void icc_frame_ready(ext_image_copy_capture_frame_v1 *frame); + void icc_frame_failed(ext_image_copy_capture_frame_v1 *frame, std::uint32_t reason); + frame_t *get_next_frame() { return current_frame == &frames[0] ? &frames[1] : &frames[0]; } @@ -169,9 +162,9 @@ namespace wl { bool init_gbm(); void cleanup_gbm(); void create_and_copy_dmabuf(zwlr_screencopy_frame_v1 *frame); + void icc_create_and_copy_dmabuf(ext_image_copy_capture_frame_v1 *frame); zwp_linux_dmabuf_v1 *dmabuf_interface {nullptr}; - const std::map> *supported_modifiers {nullptr}; struct { bool supported {false}; @@ -188,6 +181,29 @@ namespace wl { std::uint32_t height; } dmabuf_info; + struct { + zwlr_screencopy_manager_v1 *manager {nullptr}; + zwlr_screencopy_frame_v1 *frame {nullptr}; + wl_output *output {nullptr}; + bool done {false}; + std::map> supported_modifiers; + } screencopy_session; + + struct { + ext_image_copy_capture_session_v1 *session {nullptr}; + ext_image_copy_capture_frame_v1 *frame {nullptr}; + std::uint32_t width {0}; + std::uint32_t height {0}; + std::uint32_t format {0}; + std::uint64_t modifier {0}; + std::uint64_t num_modifiers {0}; + std::uint64_t *modifiers; + std::map> supported_modifiers; + bool dmabuf_supported {false}; + bool cursor {false}; + bool done {false}; + } icc_session; + struct gbm_device *gbm_device {nullptr}; struct gbm_bo *current_bo {nullptr}; struct wl_buffer *current_wl_buffer {nullptr}; @@ -313,8 +329,10 @@ namespace wl { */ enum interface_e { XDG_OUTPUT, ///< xdg-output - WLR_EXPORT_DMABUF, ///< screencopy manager + WLR_SCREENCOPY, ///< wlr-screencopy manager LINUX_DMABUF, ///< linux-dmabuf protocol + EXT_IMAGE_COPY_CAPTURE, ///< ext-image-copy-capture manager + EXT_IMAGE_CAPTURE_SOURCE, ///< ext-image-capture-source manager MAX_INTERFACES, ///< Maximum number of interfaces }; @@ -359,11 +377,12 @@ namespace wl { */ void dmabuf_modifier(zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo); - std::vector> monitors; ///< Outputs discovered from the Wayland registry. - std::map> supported_modifiers; ///< DRM format modifiers grouped by format. - zwlr_screencopy_manager_v1 *screencopy_manager {nullptr}; ///< WLR screencopy global used to request frames. - zwp_linux_dmabuf_v1 *dmabuf_interface {nullptr}; ///< Linux DMA-BUF global used to allocate frame buffers. - zxdg_output_manager_v1 *output_manager {nullptr}; ///< xdg-output global used to query monitor names and sizes. + std::vector> monitors; + zwlr_screencopy_manager_v1 *screencopy_manager {nullptr}; + zwp_linux_dmabuf_v1 *dmabuf_interface {nullptr}; + zxdg_output_manager_v1 *output_manager {nullptr}; + ext_image_copy_capture_manager_v1 *icc_manager {nullptr}; + ext_output_image_capture_source_manager_v1 *icc_source_manager {nullptr}; private: void add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version); @@ -371,7 +390,6 @@ namespace wl { std::bitset interface; wl_registry_listener listener; - zwp_linux_dmabuf_v1_listener dmabuf_listener; }; /** diff --git a/src/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp index 65020d6262f..08b70352bdd 100644 --- a/src/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -70,37 +70,34 @@ namespace wl { return -1; } - if (!interface[wl::interface_t::WLR_EXPORT_DMABUF]) { - BOOST_LOG(error) << "[wlgrab] Missing Wayland wire for wlr-export-dmabuf"sv; + if (!interface[wl::interface_t::WLR_SCREENCOPY] && !interface[wl::interface_t::EXT_IMAGE_COPY_CAPTURE]) { + BOOST_LOG(warning) << "Missing Wayland wire for wlr-screencopy AND ext-image-copy-capture"sv; return -1; } - // Populate xdg_output info (name, viewport) for every monitor up - // front so we can match by stable name in addition to index. - for (auto &m : interface.monitors) { - m->listen(interface.output_manager); + // find all wayland outputs again if a new one was just created + for (auto &monitor : interface.monitors) { + monitor->listen(interface.output_manager); } + display.roundtrip(); auto monitor = interface.monitors[0].get(); if (!display_name.empty()) { - // Match by xdg_output name first (stable across hotplug, e.g. - // "eDP-1", "HEADLESS-2"). Fall back to numeric index for - // backward compatibility with existing configs. - bool matched = false; - for (auto &m : interface.monitors) { - if (m->name == display_name) { - monitor = m.get(); - matched = true; - break; - } - } - if (!matched) { + // support both display index and connector name + if (std::all_of(display_name.begin(), display_name.end(), ::isdigit)) { auto streamedMonitor = util::from_view(display_name); if (streamedMonitor >= 0 && streamedMonitor < interface.monitors.size()) { monitor = interface.monitors[streamedMonitor].get(); } + } else { + auto it = std::find_if(interface.monitors.begin(), interface.monitors.end(), [&display_name](std::unique_ptr &m) { + return m.get()->name == display_name; + }); + if (it != interface.monitors.end()) { + monitor = (*it).get(); + } } } @@ -160,8 +157,20 @@ namespace wl { inline platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) { auto to = std::chrono::steady_clock::now() + timeout; - // Dispatch events until we get a new frame or the timeout expires - dmabuf.listen(interface.screencopy_manager, interface.dmabuf_interface, &interface.supported_modifiers, output, cursor); + // Prefer ext-image-copy-capture over wlr-screencopy + if (interface.icc_manager && interface.icc_source_manager) { + if (dmabuf.status == dmabuf_t::REINIT) { + BOOST_LOG(info) << "Creating ext-image-copy-capture session"; + dmabuf.icc_create(interface.icc_manager, interface.icc_source_manager, interface.dmabuf_interface, output, cursor); + } + dmabuf.icc_capture(cursor); + } else { + if (dmabuf.status == dmabuf_t::REINIT) { + BOOST_LOG(info) << "Creating wlr-screencopy session"; + dmabuf.screencopy_create(interface.screencopy_manager, interface.dmabuf_interface, output); + } + dmabuf.screencopy_capture(cursor); + } do { auto remaining_time_ms = std::chrono::duration_cast(to - std::chrono::steady_clock::now()); if (remaining_time_ms.count() < 0 || !display.dispatch(remaining_time_ms)) { @@ -548,8 +557,8 @@ namespace platf { return {}; } - if (!interface[wl::interface_t::WLR_EXPORT_DMABUF]) { - BOOST_LOG(warning) << "[wlgrab] Missing Wayland wire for wlr-export-dmabuf"sv; + if (!interface[wl::interface_t::WLR_SCREENCOPY] && !interface[wl::interface_t::EXT_IMAGE_COPY_CAPTURE]) { + BOOST_LOG(warning) << "Missing Wayland wire for wlr-screencopy AND ext-image-copy-capture"sv; return {}; } diff --git a/src/video.cpp b/src/video.cpp index b6ea562495f..144ec4b8866 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -1493,6 +1493,14 @@ namespace video { return; } } + + // If output_name is a connector name (e.g. "DP-2") rather than a numeric + // index, it won't match the numeric display_names list. Add it directly + // so display_t::init() can resolve the connector name. + if (!output_name.empty()) { + display_names.emplace_back(output_name); + current_display_index = display_names.size() - 1; + } } } diff --git a/src_assets/common/assets/web/configs/tabs/Advanced.vue b/src_assets/common/assets/web/configs/tabs/Advanced.vue index 108d2b3362d..85d9964ec50 100644 --- a/src_assets/common/assets/web/configs/tabs/Advanced.vue +++ b/src_assets/common/assets/web/configs/tabs/Advanced.vue @@ -65,13 +65,13 @@ const config = ref(props.config)