diff --git a/src/seat/helper.cpp b/src/seat/helper.cpp index 707b9721c1..1b9a200899 100644 --- a/src/seat/helper.cpp +++ b/src/seat/helper.cpp @@ -3452,30 +3452,13 @@ void Helper::handleNewForeignToplevelCaptureRequest(wlr_ext_foreign_toplevel_ima return; } - WSurfaceItem *surfaceItem = surfaceWrapper->surfaceItem(); - if (!surfaceItem) { - qCWarning(lcTlCapture) << "Could not get WSurfaceItem from SurfaceWrapper"; - return; - } - - WSurfaceItemContent *surfaceContent = surfaceItem->findItemContent(); - if (!surfaceContent) { - qCWarning(lcTlCapture) << "Could not find WSurfaceItemContent"; - return; - } - - qCDebug(lcTlCapture) << "Found WSurfaceItemContent for capture:" - << "size=" << surfaceContent->size() - << "implicitSize=" << QSizeF(surfaceContent->implicitWidth(), surfaceContent->implicitHeight()) - << "isTextureProvider=" << surfaceContent->isTextureProvider(); - auto *output = surfaceWrapper->ownsOutput()->output(); if (!output) { qCWarning(lcTlCapture) << "Could not get WOutput from SurfaceWrapper"; return; } - auto *imageCaptureSource = new WExtImageCaptureSourceV1Impl(surfaceContent, output); + auto *imageCaptureSource = new WExtImageCaptureSourceV1Impl(surfaceWrapper->surfaceItem(), output); bool success = qw_ext_foreign_toplevel_image_capture_source_manager_v1::request_accept( request, *imageCaptureSource); diff --git a/waylib/src/server/qtquick/woutputrenderwindow.cpp b/waylib/src/server/qtquick/woutputrenderwindow.cpp index 454167897d..ed26973cbd 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.cpp +++ b/waylib/src/server/qtquick/woutputrenderwindow.cpp @@ -1903,6 +1903,43 @@ bool WOutputRenderWindow::inRendering() const return d->inRendering; } +qw_buffer *WOutputRenderWindow::renderItemToBuffer(WBufferRenderer *renderer, + const QList &renderMatrices, + const QSize &pixelSize, + qreal dpr, + uint32_t format) +{ + Q_D(WOutputRenderWindow); + Q_ASSERT(renderer); + Q_ASSERT(!renderMatrices.isEmpty()); + + auto buffer = renderer->beginRender( + pixelSize, + dpr, + format, + WBufferRenderer::RenderFlags( + WBufferRenderer::DontConfigureSwapchain + | WBufferRenderer::RedirectOpenGLContextDefaultFrameBufferObject)); + if (!buffer) + return nullptr; + + const int pushedCount = renderMatrices.size(); + for (int i = 0; i < pushedCount; i++) { + d->pushRenderer(renderer); + renderer->render(i, renderMatrices[i], { }, { }, i > 0); + } + + renderer->endRender(); + + // Pop all entries we pushed (one per render call) to keep rendererList clean. + for (int i = 0; i < pushedCount; i++) { + if (!d->rendererList.isEmpty()) + d->rendererList.pop(); + } + + return renderer->lastBuffer(); +} + void WOutputRenderWindow::setRenderEnabled(bool enabled) { Q_D(WOutputRenderWindow); d->renderEnabled = enabled; diff --git a/waylib/src/server/qtquick/woutputrenderwindow.h b/waylib/src/server/qtquick/woutputrenderwindow.h index 41e61dbbf7..624d868ddf 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.h +++ b/waylib/src/server/qtquick/woutputrenderwindow.h @@ -6,6 +6,9 @@ #include #include #include +QW_BEGIN_NAMESPACE +class qw_buffer; +QW_END_NAMESPACE #include #include @@ -59,6 +62,17 @@ class WAYLIB_SERVER_EXPORT WOutputRenderWindow : public QQuickWindow, public QQm WBufferRenderer *currentRenderer() const; bool inRendering() const; + // Render source items to an offscreen buffer via the given WBufferRenderer. + // Must be called during the render pass (e.g. in afterRendering signal). + // renderMatrices: one per source in renderer->sourceList(), maps source + // local coords to render buffer pixel coords. Returns the rendered buffer + // (same as renderer->lastBuffer()), or nullptr on failure. + QW_NAMESPACE::qw_buffer *renderItemToBuffer(WBufferRenderer *renderer, + const QList &renderMatrices, + const QSize &pixelSize, + qreal dpr, + uint32_t format); + void setRenderEnabled(bool enabled); static QList> paintOrderItemList(QQuickItem *root, std::function filter); diff --git a/waylib/src/server/utils/wextimagecapturesourcev1impl.cpp b/waylib/src/server/utils/wextimagecapturesourcev1impl.cpp index 901d2af258..01f04f9999 100644 --- a/waylib/src/server/utils/wextimagecapturesourcev1impl.cpp +++ b/waylib/src/server/utils/wextimagecapturesourcev1impl.cpp @@ -5,6 +5,8 @@ #include "wsurfaceitem.h" #include "wsgtextureprovider.h" #include "woutputrenderwindow.h" +#include "wbufferrenderer_p.h" +#include "wquicktextureproxy.h" #include "woutput.h" #include "wtools.h" #include "wayliblogging.h" @@ -16,6 +18,8 @@ #include #include +#include + #include extern "C" { @@ -36,26 +40,29 @@ WAYLIB_SERVER_BEGIN_NAMESPACE struct ConstraintBuilder { wlr_ext_image_capture_source_v1 *source; WOutput *output; - - ConstraintBuilder(wlr_ext_image_capture_source_v1 *src, WOutput *out) - : source(src), output(out) {} - + + ConstraintBuilder(wlr_ext_image_capture_source_v1 *src, WOutput *out) + : source(src) + , output(out) + { + } + void setSize(int width, int height) { source->width = width; source->height = height; } - + void buildShmFormats() { auto renderer = output->renderer(); auto swapchain = output->swapchain(); uint32_t format = DRM_FORMAT_ARGB8888; // fallback - + if (renderer && swapchain) { struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain->handle()); if (buffer) { struct wlr_texture *texture = wlr_texture_from_buffer(renderer->handle(), buffer); wlr_buffer_unlock(buffer); - + if (texture) { uint32_t shm_format = wlr_texture_preferred_read_format(texture); wlr_texture_destroy(texture); @@ -65,38 +72,35 @@ struct ConstraintBuilder { } } } - - // Use unique_ptr for safer memory management + auto formats = std::unique_ptr(new uint32_t[1]); formats[0] = format; - + free(source->shm_formats); source->shm_formats = formats.release(); source->shm_formats_len = 1; - + qCDebug(lcWlImageCapture) << "Set SHM format:" << format; } - + void buildDmabufFormats() { auto renderer = output->renderer(); auto swapchain = output->swapchain(); - + if (!renderer || !swapchain) return; - + int drm_fd = wlr_renderer_get_drm_fd(renderer->handle()); - if (swapchain->handle()->allocator && - (swapchain->handle()->allocator->buffer_caps & WLR_BUFFER_CAP_DMABUF) && - drm_fd >= 0) { - + if (swapchain->handle()->allocator + && (swapchain->handle()->allocator->buffer_caps & WLR_BUFFER_CAP_DMABUF) + && drm_fd >= 0) { + struct stat dev_stat; if (fstat(drm_fd, &dev_stat) == 0) { source->dmabuf_device = dev_stat.st_rdev; - - // Clean up old DMA-BUF formats + wlr_drm_format_set_finish(&source->dmabuf_formats); source->dmabuf_formats = (struct wlr_drm_format_set){}; - - // Copy DMA-BUF formats from swapchain + for (size_t i = 0; i < swapchain->handle()->format.len; i++) { wlr_drm_format_set_add(&source->dmabuf_formats, swapchain->handle()->format.format, swapchain->handle()->format.modifiers[i]); @@ -105,284 +109,326 @@ struct ConstraintBuilder { } } } - + void apply() { wl_signal_emit_mutable(&source->events.constraints_update, nullptr); } }; -WExtImageCaptureSourceV1Impl::WExtImageCaptureSourceV1Impl(WSurfaceItemContent *surfaceContent, WOutput *output) - : QObject(surfaceContent) // TODO: Check if Qt object tree destruction timing is appropriate - , m_surfaceContent(surfaceContent) +WExtImageCaptureSourceV1Impl::WExtImageCaptureSourceV1Impl(QQuickItem *surfaceItem, + WOutput *output) + : QObject(surfaceItem) + , m_surfaceItem(surfaceItem) , m_output(output) , m_capturing(false) - , m_renderEndConnection() { - Q_ASSERT(m_surfaceContent); + Q_ASSERT(m_surfaceItem); + Q_ASSERT(m_output); - // Initialize wlr_ext_image_capture_source_v1 wlr_ext_image_capture_source_v1_init(handle(), impl()); - - // Get actual surface size and set constraints directly - auto surface = m_surfaceContent->surface(); - if (surface && surface->handle()) { - auto wlr_surface = surface->handle()->handle(); - int width = wlr_surface->current.width; - int height = wlr_surface->current.height; - - // Validate dimensions before setting constraints - if (width > 0 && height > 0) { - // Use constraint builder helper directly - ConstraintBuilder builder(handle(), m_output); - builder.setSize(width, height); - builder.buildShmFormats(); - builder.buildDmabufFormats(); - builder.apply(); - - qCDebug(lcWlImageCapture) << "Initial constraints set successfully:"; - qCDebug(lcWlImageCapture) << " - Width:" << width; - qCDebug(lcWlImageCapture) << " - Height:" << height; - } else { - qCWarning(lcWlImageCapture) << "Invalid surface dimensions for constraints:" << width << "x" << height; - } + + // Set initial constraints from the surfaceItem's bounding rect * dpr + const auto pixelSize = computePixelSize(); + if (pixelSize.isValid() && !pixelSize.isEmpty()) { + ConstraintBuilder builder(handle(), m_output); + builder.setSize(pixelSize.width(), pixelSize.height()); + builder.buildShmFormats(); + builder.buildDmabufFormats(); + builder.apply(); + + qCDebug(lcWlImageCapture) << "Initial constraints set:" << pixelSize; } else { - qCWarning(lcWlImageCapture) << "No valid surface available for setting initial constraints"; + qCWarning(lcWlImageCapture) << "Invalid surface dimensions for constraints:" << pixelSize; } } WExtImageCaptureSourceV1Impl::~WExtImageCaptureSourceV1Impl() { if (m_capturing) { - qCDebug(lcWlImageCapture) << "WExtImageCaptureSourceV1Impl destroyed while capturing"; + stop(); } + // m_captureRenderer is parented to renderWindow()->contentItem(), not + // m_surfaceItem, so it may outlive m_surfaceItem. QPointer handles + // the case where the render window was destroyed first. Deleting the + // WBufferRenderer also deletes the proxy (its child), which in turn + // calls derefFromEffectItem on m_surfaceItem via ~WQuickTextureProxy. + if (m_captureRenderer) { + delete m_captureRenderer; + } + m_proxy = nullptr; +} + +WOutputRenderWindow *WExtImageCaptureSourceV1Impl::renderWindow() const +{ + if (!m_surfaceItem) + return nullptr; + return qobject_cast(m_surfaceItem->window()); +} + +qreal WExtImageCaptureSourceV1Impl::computeDpr() const +{ + auto rw = renderWindow(); + return rw ? rw->effectiveDevicePixelRatio() : 1.0; +} + +QSize WExtImageCaptureSourceV1Impl::computePixelSize() const +{ + if (!m_surfaceItem) + return { }; + + const auto sz = m_surfaceItem->size(); + if (sz.isEmpty()) + return { }; + + const qreal dpr = computeDpr(); + return QSize(qCeil(sz.width() * dpr), qCeil(sz.height() * dpr)); +} + +void WExtImageCaptureSourceV1Impl::updateConstraints(const QSize &pixelSize) +{ + if (!pixelSize.isValid() || pixelSize.isEmpty()) + return; + + ConstraintBuilder builder(handle(), m_output); + builder.setSize(pixelSize.width(), pixelSize.height()); + builder.buildShmFormats(); + builder.buildDmabufFormats(); + builder.apply(); } void WExtImageCaptureSourceV1Impl::start([[maybe_unused]] bool with_cursors) { - // TODO: Implement cursor capture if needed m_capturing = true; qCDebug(lcWlImageCapture) << "WExtImageCaptureSourceV1Impl::start() with_cursors:" << with_cursors; - // TODO: Optimize multiple clients capturing the same window - // Currently each client creates its own WExtImageCaptureSourceV1Impl instance, - // which means multiple render listeners for the same surface. Consider implementing - // a manager to share render events among multiple capture sources. - - if (!m_surfaceContent) { - qCWarning(lcWlImageCapture) << "No surface content available for capture"; - return; - } - - // Get render window - auto textureProvider = m_surfaceContent->wTextureProvider(); - if (!textureProvider) { - qCWarning(lcWlImageCapture) << "No texture provider available for start"; - return; - } - - auto renderWindow = textureProvider->window(); - if (!renderWindow) { + auto rw = renderWindow(); + if (!rw) { qCWarning(lcWlImageCapture) << "No render window available for start"; return; } - - // Connect to renderEnd signal - m_renderEndConnection = connect(renderWindow, - &WOutputRenderWindow::renderEnd, - this, - &WExtImageCaptureSourceV1Impl::handleRenderEnd, - Qt::AutoConnection); - - if (!m_renderEndConnection) { - qCWarning(lcWlImageCapture) << "Cannot connect to render end of output render window"; - } - - // If not currently rendering, trigger immediately - if (!renderWindow->inRendering()) { - QMetaObject::invokeMethod(this, &WExtImageCaptureSourceV1Impl::handleRenderEnd, Qt::AutoConnection); + + // Create the offscreen WBufferRenderer parented to contentItem (root), + // and a WQuickTextureProxy as intermediary. The proxy's source is + // m_surfaceItem (WSurfaceItem) — the proxy enables a layer on it and + // exposes its texture, so WBufferRenderer renders only a simple image node + // instead of traversing the entire output's scene graph subtree. Because + // the layer is on WSurfaceItem (not SurfaceWrapper), the shadow/decoration + // nodes are unaffected — they live outside WSurfaceItem's subtree. + if (!m_captureRenderer) { + m_captureRenderer = new WBufferRenderer(rw->contentItem()); + m_captureRenderer->setOutput(m_output); + m_captureRenderer->setVisible(false); + + m_proxy = new WQuickTextureProxy(m_captureRenderer); + m_proxy->setSourceItem(m_surfaceItem); + m_captureRenderer->setSourceList({ m_proxy }, false); } + + // Phase 1: offscreen render during the Qt RHI frame (afterRendering) + m_afterRenderingConnection = connect(rw, + &QQuickWindow::afterRendering, + this, + &WExtImageCaptureSourceV1Impl::doOffscreenRender, + Qt::AutoConnection); + + // Phase 2: emit frame event + copy_buffer after endFrame + resetGlState (renderEnd) + m_renderEndConnection = connect(rw, + &WOutputRenderWindow::renderEnd, + this, + &WExtImageCaptureSourceV1Impl::handleRenderEnd, + Qt::AutoConnection); + + // Trigger first frame + wlr_output_update_needs_frame(m_output->nativeHandle()); } void WExtImageCaptureSourceV1Impl::stop() { m_capturing = false; qCDebug(lcWlImageCapture) << "WExtImageCaptureSourceV1Impl::stop()"; - - // Disconnect render end connection + + if (m_afterRenderingConnection) { + disconnect(m_afterRenderingConnection); + m_afterRenderingConnection = QMetaObject::Connection(); + } if (m_renderEndConnection) { disconnect(m_renderEndConnection); m_renderEndConnection = QMetaObject::Connection(); } + + if (m_renderedBuffer) { + m_renderedBuffer->unlock(); + } + m_renderedBuffer = nullptr; } void WExtImageCaptureSourceV1Impl::schedule_frame() { qCDebug(lcWlImageCapture) << "WExtImageCaptureSourceV1Impl::schedule_frame()"; - + if (!m_capturing) { qCWarning(lcWlImageCapture) << "schedule_frame called but not capturing"; return; } - - if (!m_surfaceContent) { - qCWarning(lcWlImageCapture) << "No surface content available for frame scheduling"; + + // Request output update to ensure next frame will be rendered. + // doOffscreenRender fires via afterRendering, handleRenderEnd via renderEnd. + wlr_output_update_needs_frame(m_output->nativeHandle()); +} + +void WExtImageCaptureSourceV1Impl::doOffscreenRender() +{ + if (!m_capturing || !m_surfaceItem || !m_captureRenderer) + return; + + auto rw = renderWindow(); + if (!rw) + return; + + const auto pixelSize = computePixelSize(); + if (pixelSize.isEmpty()) { + qCWarning(lcWlImageCapture) << "Invalid pixel size for offscreen render:" << pixelSize; return; } - - // Request output update to ensure next frame will be rendered - wlr_output_update_needs_frame(m_output->nativeHandle()); - - // Get render window to check if currently rendering - auto textureProvider = m_surfaceContent->wTextureProvider(); - if (textureProvider) { - auto renderWindow = textureProvider->window(); - if (renderWindow && !renderWindow->inRendering()) { - QMetaObject::invokeMethod(this, &WExtImageCaptureSourceV1Impl::handleRenderEnd, Qt::AutoConnection); - } + const qreal dpr = computeDpr(); + + // Sync proxy size to the source's logical size (window body, no shadow). + // The proxy's layer texture is WSurfaceItem::width × height, so setting + // the proxy rect to the same size yields 1:1 texture mapping — no stretch. + const auto sz = m_surfaceItem->size(); + if (m_proxy) { + m_proxy->setWidth(sz.width()); + m_proxy->setHeight(sz.height()); } - - qCDebug(lcWlImageCapture) << "Scheduled frame capture"; + + // The proxy is at (0,0) within the WBufferRenderer. The renderer's + // setDevicePixelRatio (called inside WBufferRenderer::render) already + // maps logical→physical coordinates, so renderMatrix must be identity — + // adding scale(dpr) here double-scales content by dpr² (visible only at + // fractional scale, e.g. 125% → content 1.5625× too large). + QMatrix4x4 renderMatrix; + + // Unlock previous buffer if it was never copied (e.g. client didn't request copy_frame) + if (m_renderedBuffer) { + m_renderedBuffer->unlock(); + m_renderedBuffer = nullptr; + } + + m_renderedBuffer = rw->renderItemToBuffer(m_captureRenderer, + { renderMatrix }, + pixelSize, + dpr, + DRM_FORMAT_ARGB8888); + + if (m_renderedBuffer) { + // Lock to prevent swapchain from recycling before copy_frame + m_renderedBuffer->lock(); + } + + qCDebug(lcWlImageCapture) << "Offscreen render done, buffer:" + << (m_renderedBuffer ? m_renderedBuffer->handle() : nullptr) + << "size:" << pixelSize; } void WExtImageCaptureSourceV1Impl::handleRenderEnd() { - qCDebug(lcWlImageCapture) << "WExtImageCaptureSourceV1Impl::handleRenderEnd() - triggering frame event"; - if (!m_capturing) { qCWarning(lcWlImageCapture) << "handleRenderEnd called but not capturing"; return; } - - // Get surface size and validate it - QSize surfaceSize = m_surfaceContent->size().toSize(); - if (surfaceSize.width() <= 0 || surfaceSize.height() <= 0) { - qCWarning(lcWlImageCapture) << "Invalid surface size for damage region:" << surfaceSize; + + if (!m_renderedBuffer || !m_renderedBuffer->handle()) { + qCWarning(lcWlImageCapture) << "No rendered buffer available for frame event"; + return; + } + + auto wlr_buf = m_renderedBuffer->handle(); + const int bufferWidth = wlr_buf->width; + const int bufferHeight = wlr_buf->height; + if (bufferWidth <= 0 || bufferHeight <= 0) { + qCWarning(lcWlImageCapture) << "Invalid buffer size:" << bufferWidth << "x" << bufferHeight; return; } - - // Create damage region with RAII - WPixmanRegion fullDamage(0, 0, surfaceSize.width(), surfaceSize.height()); - - // Create frame event and Q_EMIT + + // TODO: partial damage + WPixmanRegion fullDamage(0, 0, bufferWidth, bufferHeight); + wlr_ext_image_capture_source_v1_frame_event event { .damage = fullDamage.get(), }; wl_signal_emit_mutable(&handle()->events.frame, &event); - - qCDebug(lcWlImageCapture) << "Frame event emitted with damage region:" << surfaceSize; + + qCDebug(lcWlImageCapture) << "Frame event emitted with damage:" << bufferWidth << "x" + << bufferHeight; } -void WExtImageCaptureSourceV1Impl::copy_frame(wlr_ext_image_copy_capture_frame_v1 *dst_frame, - [[maybe_unused]] wlr_ext_image_capture_source_v1_frame_event *frame_event) +void WExtImageCaptureSourceV1Impl::copy_frame( + wlr_ext_image_copy_capture_frame_v1 *dst_frame, + [[maybe_unused]] wlr_ext_image_capture_source_v1_frame_event *frame_event) { qCDebug(lcWlImageCapture) << "WExtImageCaptureSourceV1Impl::copy_frame()"; - + if (!m_capturing) { qCWarning(lcWlImageCapture) << "copy_frame called but not capturing"; qw_ext_image_copy_capture_frame_v1::from(dst_frame)->fail(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); return; } - - if (!m_surfaceContent) { - qCWarning(lcWlImageCapture) << "No surface content available for frame copy"; - qw_ext_image_copy_capture_frame_v1::from(dst_frame)->fail(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); - return; - } - - // Get texture provider - auto textureProvider = m_surfaceContent->wTextureProvider(); - if (!textureProvider) { - qCWarning(lcWlImageCapture) << "No texture provider available"; - qw_ext_image_copy_capture_frame_v1::from(dst_frame)->fail(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); - return; - } - auto buffer = textureProvider->qwBuffer(); - if (!buffer || !buffer->handle()) { - qCWarning(lcWlImageCapture) << "No internal buffer available"; + if (!m_renderedBuffer || !m_renderedBuffer->handle()) { + qCWarning(lcWlImageCapture) << "No rendered buffer available for copy"; qw_ext_image_copy_capture_frame_v1::from(dst_frame)->fail(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); return; } - // Lock the buffer for the duration of the copy to prevent races during resize - if (!buffer->lock()) { - qCWarning(lcWlImageCapture) << "Failed to lock internal buffer"; - qw_ext_image_copy_capture_frame_v1::from(dst_frame)->fail(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); - return; - } - std::unique_ptr bufferGuard(buffer); - - // Get renderer - auto renderWindow = textureProvider->window(); - if (!renderWindow) { - qCWarning(lcWlImageCapture) << "No render window available"; - qw_ext_image_copy_capture_frame_v1::from(dst_frame)->fail(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); - return; - } - auto renderer = m_output->renderer(); if (!renderer) { qCWarning(lcWlImageCapture) << "No renderer available"; qw_ext_image_copy_capture_frame_v1::from(dst_frame)->fail(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); return; } - - // Prefer the client buffer source if present - wlr_buffer *src = buffer->handle(); - if (auto clientBuf = wlr_client_buffer_get(*buffer)) { - src = clientBuf->source; - } - // Critical safety checks: validate all required pointers and buffers before copying - if (!src) { - qCWarning(lcWlImageCapture) << "Source buffer is null, cannot copy frame"; - if (dst_frame) { - qw_ext_image_copy_capture_frame_v1::from(dst_frame)->fail(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS); - } - return; - } + auto src = m_renderedBuffer->handle(); + // Buffer is already locked in doOffscreenRender; unlock after copy. + // copy_buffer calls fail() + frame_destroy + free internally on failure, + // so do NOT touch dst_frame afterwards. if (!dst_frame || !dst_frame->buffer) { - qCWarning(lcWlImageCapture) << "Destination frame or buffer is null, cannot copy"; + qCWarning(lcWlImageCapture) << "Destination frame or buffer is null"; if (dst_frame) { qw_ext_image_copy_capture_frame_v1::from(dst_frame)->fail(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS); } + m_renderedBuffer->unlock(); + m_renderedBuffer = nullptr; return; } - // Validate buffer dimensions to prevent crashes during resize + // Validate buffer dimensions if (dst_frame->buffer->width != src->width || dst_frame->buffer->height != src->height) { - qCWarning(lcWlImageCapture) << "Buffer size mismatch during resize (dst:" << dst_frame->buffer->width << "x" << dst_frame->buffer->height - << ", src:" << src->width << "x" << src->height << "), updating constraints"; - - // Update constraints when we detect a size mismatch - ConstraintBuilder builder(handle(), m_output); - builder.setSize(src->width, src->height); - builder.buildShmFormats(); - builder.buildDmabufFormats(); - builder.apply(); - - qCDebug(lcWlImageCapture) << "Constraints updated to new size:" << src->width << "x" << src->height; - - // Check again after constraints update - the client might have already provided a correctly sized buffer + qCWarning(lcWlImageCapture) << "Buffer size mismatch (dst:" << dst_frame->buffer->width + << "x" << dst_frame->buffer->height << ", src:" << src->width + << "x" << src->height << "), updating constraints"; + + updateConstraints(QSize(src->width, src->height)); + if (dst_frame->buffer->width != src->width || dst_frame->buffer->height != src->height) { - qCDebug(lcWlImageCapture) << "Buffer size still mismatched after constraint update, skipping frame"; + qCDebug(lcWlImageCapture) << "Buffer size still mismatched after constraint update"; qw_ext_image_copy_capture_frame_v1::from(dst_frame)->fail(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS); + m_renderedBuffer->unlock(); + m_renderedBuffer = nullptr; return; } - - qCDebug(lcWlImageCapture) << "Buffer size now matches after constraint update, proceeding with copy"; } - // Use wlroots image copy function with validated buffers bool success = qw_ext_image_copy_capture_frame_v1::copy_buffer(dst_frame, src, renderer->handle()); qCDebug(lcWlImageCapture) << "Copy result:" << success; - + + // Unlock regardless of success/failure — the buffer is no longer needed + m_renderedBuffer->unlock(); + m_renderedBuffer = nullptr; + if (success) { - // Successfully copied, mark frame as ready struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); - qw_ext_image_copy_capture_frame_v1::from(dst_frame)->ready(WL_OUTPUT_TRANSFORM_NORMAL, &now); qCDebug(lcWlImageCapture) << "Frame copy successful"; } else { @@ -392,28 +438,12 @@ void WExtImageCaptureSourceV1Impl::copy_frame(wlr_ext_image_copy_capture_frame_v qCWarning(lcWlImageCapture) << " - Unsupported buffer format"; qCWarning(lcWlImageCapture) << " - Renderer issues"; qCWarning(lcWlImageCapture) << " - Memory access problems"; - - // Check if it's a buffer constraints issue - if (dst_frame->buffer && buffer->handle()) { - if (dst_frame->buffer->width != buffer->handle()->width || - dst_frame->buffer->height != buffer->handle()->height) { - qCWarning(lcWlImageCapture) << "Buffer size mismatch detected, using BUFFER_CONSTRAINTS failure reason"; - qw_ext_image_copy_capture_frame_v1::from(dst_frame)->fail(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS); - return; - } - } - - // For other failures, use UNKNOWN reason - qw_ext_image_copy_capture_frame_v1::from(dst_frame)->fail(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); } } wlr_ext_image_capture_source_v1_cursor *WExtImageCaptureSourceV1Impl::get_pointer_cursor([[maybe_unused]] wlr_seat *seat) { qCDebug(lcWlImageCapture) << "WExtImageCaptureSourceV1Impl::get_pointer_cursor()"; - // TODO: Implement cursor retrieval logic - // This needs to get cursor information from seat and create corresponding cursor structure - // Currently return nullptr to indicate no cursor information return nullptr; } diff --git a/waylib/src/server/utils/wextimagecapturesourcev1impl.h b/waylib/src/server/utils/wextimagecapturesourcev1impl.h index 3e93f47683..e43704fc7d 100644 --- a/waylib/src/server/utils/wextimagecapturesourcev1impl.h +++ b/waylib/src/server/utils/wextimagecapturesourcev1impl.h @@ -7,6 +7,8 @@ #include #include +#include +#include QW_BEGIN_NAMESPACE class qw_buffer; @@ -14,14 +16,18 @@ QW_END_NAMESPACE WAYLIB_SERVER_BEGIN_NAMESPACE -class WSurfaceItemContent; class WOutput; +class WOutputRenderWindow; +class WQuickTextureProxy; +class WBufferRenderer; class WAYLIB_SERVER_EXPORT WExtImageCaptureSourceV1Impl : public QObject, public QW_NAMESPACE::qw_ext_image_capture_source_v1_interface { Q_OBJECT public: - explicit WExtImageCaptureSourceV1Impl(WSurfaceItemContent *surfaceContent, WOutput *output); + // surfaceItem is the WSurfaceItem (includes surface content + subsurfaces + titleBar, + // but excludes shadow/decoration which live on the parent SurfaceWrapper). + explicit WExtImageCaptureSourceV1Impl(QQuickItem *surfaceItem, WOutput *output); ~WExtImageCaptureSourceV1Impl(); QW_INTERFACE(start, void, bool with_cursors); @@ -32,13 +38,27 @@ class WAYLIB_SERVER_EXPORT WExtImageCaptureSourceV1Impl : public QObject, public QW_INTERFACE(get_pointer_cursor, wlr_ext_image_capture_source_v1_cursor *, wlr_seat *seat); private Q_SLOTS: + // Phase 1 (afterRendering): offscreen render via WBufferRenderer + void doOffscreenRender(); + // Phase 2 (renderEnd): emit frame event, copy_frame uses m_renderedBuffer void handleRenderEnd(); private: - QPointer m_surfaceContent; + WOutputRenderWindow *renderWindow() const; + qreal computeDpr() const; + QSize computePixelSize() const; + void updateConstraints(const QSize &pixelSize); + + QPointer m_surfaceItem; WOutput *m_output; bool m_capturing; + QMetaObject::Connection m_afterRenderingConnection; QMetaObject::Connection m_renderEndConnection; + + QPointer m_captureRenderer; + QPointer m_proxy; + // Buffer from offscreen render, valid between doOffscreenRender and copy_frame + QPointer m_renderedBuffer; }; WAYLIB_SERVER_END_NAMESPACE