Skip to content

refactor: 窗口录屏 - #1234

Open
glyvut wants to merge 1 commit into
linuxdeepin:masterfrom
glyvut:refactor/rec
Open

refactor: 窗口录屏#1234
glyvut wants to merge 1 commit into
linuxdeepin:masterfrom
glyvut:refactor/rec

Conversation

@glyvut

@glyvut glyvut commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary by Sourcery

Refactor window recording to capture surface items through an isolated offscreen rendering path.

New Features:

  • Support capturing complete window item subtrees, including content and subsurfaces, into offscreen buffers.

Bug Fixes:

  • Improve capture buffer lifetime and frame-copy handling by retaining rendered buffers until they are copied.

Enhancements:

  • Refactor foreign toplevel image capture to render directly from the surface item and derive capture dimensions from item size and device pixel ratio.

@deepin-ci-robot

Copy link
Copy Markdown

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@sourcery-ai

sourcery-ai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Reviewer's Guide

Foreign toplevel image capture now renders the full Qt Quick surface-item subtree, including subsurfaces, into a dedicated DPR-aware offscreen buffer. Capture scheduling, constraints, full-frame damage, and buffer locking are coordinated across afterRendering, renderEnd, and copy_frame to improve reliability and lifetime safety.

Sequence diagram for foreign toplevel offscreen capture

sequenceDiagram
    participant Client
    participant CaptureSource
    participant Output
    participant Renderer
    participant Buffer

    Client->>CaptureSource: start()
    CaptureSource->>Output: wlr_output_update_needs_frame()
    Output-->>CaptureSource: afterRendering
    CaptureSource->>CaptureSource: computePixelSize()
    CaptureSource->>Output: renderItemToBuffer(renderer, surfaceItem, pixelSize, dpr, format)
    Output->>Renderer: beginRender()
    Renderer->>Renderer: renderTransientItem(surfaceItem)
    Renderer->>Buffer: render full item subtree
    Renderer-->>Output: lastBuffer()
    Output-->>CaptureSource: renderedBuffer
    CaptureSource->>Buffer: wlr_buffer_lock()
    Output-->>CaptureSource: renderEnd
    CaptureSource-->>Client: frame event with full damage
    Client->>CaptureSource: copy_frame()
    CaptureSource->>Buffer: wlr_ext_image_copy_capture_frame_v1_copy_buffer()
    CaptureSource->>Buffer: wlr_buffer_unlock()
Loading

Flow diagram for capture buffer lifetime and resize handling

flowchart TD
    A[start capture] --> B[afterRendering]
    B --> C[Compute pixel size using item size and DPR]
    C --> D[renderItemToBuffer]
    D --> E[Lock rendered buffer]
    E --> F[renderEnd]
    F --> G[Emit frame event with full buffer damage]
    G --> H[copy_frame]
    H --> I{Destination size matches?}
    I -- No --> J[updateConstraints]
    J --> K{Size matches after update?}
    K -- No --> L[Fail frame and unlock buffer]
    K -- Yes --> M[Copy rendered buffer]
    I -- Yes --> M
    M --> N[Unlock buffer]
    N --> O[Frame complete]
Loading

File-Level Changes

Change Details Files
Route foreign toplevel capture through the complete Qt Quick surface item subtree instead of reusing the surface content buffer.
  • Pass the surface item to the capture source.
  • Render the item and its subsurfaces under a temporary scene-graph root.
  • Expose output-window support for isolated item rendering into an offscreen buffer.
src/seat/helper.cpp
waylib/src/server/qtquick/private/wbufferrenderer.cpp
waylib/src/server/qtquick/private/wbufferrenderer_p.h
waylib/src/server/qtquick/woutputrenderwindow.cpp
waylib/src/server/qtquick/woutputrenderwindow.h
waylib/src/server/utils/wextimagecapturesourcev1impl.cpp
waylib/src/server/utils/wextimagecapturesourcev1impl.h
Rework capture-frame scheduling and buffer ownership around the offscreen render lifecycle.
  • Render during QQuickWindow::afterRendering and notify frames during renderEnd.
  • Keep the rendered buffer locked from rendering through copy_frame, then unlock it on every completion path.
  • Update capture constraints from device-pixel-ratio-scaled item dimensions and report full-buffer damage.
waylib/src/server/utils/wextimagecapturesourcev1impl.cpp
waylib/src/server/utils/wextimagecapturesourcev1impl.h
Add device-pixel-ratio-aware transient rendering and configure the renderer for the active render target.
  • Set pixel size, device ratio, viewport, projection, and render-target state for offscreen rendering.
  • Handle RHI/software paths and force complete render-target damage.
  • Restore the item’s original scene-graph parent and transform after rendering.
waylib/src/server/qtquick/private/wbufferrenderer.cpp
waylib/src/server/qtquick/woutputrenderwindow.cpp

Possibly linked issues

  • #unknown: The PR directly refactors the implicated image-capture path and improves buffer lifetime, synchronization, and null-risk handling.
  • #unknown: PR直接修改窗口录屏捕获路径及缓冲区同步,针对该录屏移动窗口崩溃问题。

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@glyvut
glyvut force-pushed the refactor/rec branch 2 times, most recently from a57222c to 6f63415 Compare August 5, 2026 05:15
@deepin-bot

deepin-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 0.8.18
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #1286

@deepin-bot

deepin-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 0.9.0
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #1312

@glyvut
glyvut force-pushed the refactor/rec branch 3 times, most recently from 3f028a8 to f99681b Compare August 25, 2026 06:42
@glyvut glyvut changed the title WIP: 录屏重构 refactor: 窗口录屏 Aug 25, 2026
@glyvut
glyvut marked this pull request as ready for review August 25, 2026 06:50

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="waylib/src/server/qtquick/private/wbufferrenderer.cpp" line_range="853-857" />
<code_context>
+    transientRenderer->setProjectionMatrix(projectionMatrix);
+    transientRenderer->setProjectionMatrixWithNativeNDC(projectionMatrixWithNativeNDC);
+
+    auto *textureRT = static_cast<QRhiTextureRenderTarget *>(state.sgRenderTarget.rt);
+    textureRT->setFlags(textureRT->flags() & ~QRhiTextureRenderTarget::PreserveColorContents);
+
+    rc->renderNextFrame(transientRenderer);
+    wd->rhi->finish();
+
+    if (auto *drc = qobject_cast<QSGDefaultRenderContext *>(state.context)) {
</code_context>
<issue_to_address>
**issue (bug_risk):** `renderTransientItem` unconditionally casts `state.sgRenderTarget.rt` to `QRhiTextureRenderTarget` and dereferences `wd->rhi`, but `beginRender` explicitly supports non-RHI paint-device targets where both are null. Software rendering therefore crashes during offscreen capture.

**Triggers:** When the output uses the software or another non-RHI render backend.

**Suggested fix:** Guard the RHI-specific render-target flag update and `finish()` call, or provide the corresponding paint-device rendering path.
</issue_to_address>

### Comment 2
<location path="waylib/src/server/qtquick/private/wbufferrenderer.cpp" line_range="866-874" />
<code_context>
+
+    wlr_damage_ring_add_whole(m_damageRing.get());
+
+    itemNode->setMatrix(savedMatrix);
+    tempRoot->removeChildNode(itemNode);
+    if (savedParent)
+        savedParent->appendChildNode(itemNode);
+
+    transientRenderer->setRootNode(nullptr);
+    delete transientRenderer;
+    delete tempRoot;
+}
+
 QSGRenderer *WBufferRenderer::ensureRenderer(int sourceIndex, QSGRenderContext *rc)
</code_context>
<issue_to_address>
**issue (bug_risk):** After rendering, the captured item node is restored with `savedParent->appendChildNode(itemNode)`, which moves it to the end of its parent's child list instead of restoring its original sibling position. Repeated captures therefore permanently change scene-graph stacking order and can make overlapping windows or items render above or below their original siblings.

**Triggers:** When the captured item has siblings under the same scene-graph parent and their visuals overlap.

**Suggested fix:** Save the original sibling position and reinsert the node at that position rather than appending it.
</issue_to_address>

### Comment 3
<location path="waylib/src/server/utils/wextimagecapturesourcev1impl.cpp" line_range="310-312" />
<code_context>
         m_renderEndConnection = QMetaObject::Connection();
     }
+
+    if (m_renderedBuffer) {
+        wlr_buffer_unlock(m_renderedBuffer);
+    }
+    m_renderedBuffer = nullptr;
 }

</code_context>
<issue_to_address>
**issue (bug_risk):** `doOffscreenRender` unlocks and replaces `m_renderedBuffer` on every output frame, even when the previous frame event has not yet been serviced by `copy_frame`. A delayed copy then reads the newest frame instead of the buffer associated with its frame event, so the client receives mismatched frame contents and damage metadata.

**Triggers:** When the client delays `copy_frame` long enough for another output render to run before the copy request is handled.

**Suggested fix:** Keep a locked buffer per outstanding frame, keyed by the frame event, and release it only after that frame's `copy_frame` completes or is cancelled.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread waylib/src/server/qtquick/private/wbufferrenderer.cpp Outdated
Comment thread waylib/src/server/qtquick/private/wbufferrenderer.cpp
Comment thread waylib/src/server/utils/wextimagecapturesourcev1impl.cpp
@glyvut
glyvut marked this pull request as draft August 25, 2026 07:02
@glyvut
glyvut force-pushed the refactor/rec branch 4 times, most recently from 2a9927d to 28189ba Compare August 26, 2026 06:59
@glyvut
glyvut marked this pull request as ready for review August 26, 2026 07:02

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="waylib/src/server/qtquick/private/wbufferrenderer.cpp" line_range="864-867" />
<code_context>
+    if (wd->rhi)
+        wd->rhi->finish();
+
+    if (auto *drc = qobject_cast<QSGDefaultRenderContext *>(state.context)) {
+        QRhiResourceUpdateBatch *batch = wd->rhi->nextResourceUpdateBatch();
+        drc->currentFrameCommandBuffer()->resourceUpdate(batch);
+    }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** The software-rendering path dereferences `wd->rhi` while flushing the resource update batch, even though the surrounding comment says software rendering has no RHI. A software-rendered capture therefore crashes in `doOffscreenRender()` after the offscreen render completes.

**Triggers:** When the output uses the Qt Quick software renderer and `QSGDefaultRenderContext` is active.

**Suggested fix:** Guard the resource-update-batch block with `wd->rhi` as well as `drc`, or use the appropriate software-renderer synchronization path.

```suggestion
    if (wd->rhi) {
        if (auto *drc = qobject_cast<QSGDefaultRenderContext *>(state.context)) {
            QRhiResourceUpdateBatch *batch = wd->rhi->nextResourceUpdateBatch();
            drc->currentFrameCommandBuffer()->resourceUpdate(batch);
        }
    }
```
</issue_to_address>

### Comment 2
<location path="waylib/src/server/utils/wextimagecapturesourcev1impl.cpp" line_range="429-438" />
<code_context>

-    // Use wlroots image copy function with validated buffers
     bool success = wlr_ext_image_copy_capture_frame_v1_copy_buffer(dst_frame, src, renderer);
     qCDebug(lcWlImageCapture) << "Copy result:" << success;

+    // Unlock regardless of success/failure.
</code_context>
<issue_to_address>
**issue (bug_risk):** When `wlr_ext_image_copy_capture_frame_v1_copy_buffer` returns false, the new code only logs the failure and never calls `wlr_ext_image_copy_capture_frame_v1_fail`. The client receives neither a successful ready event nor a failure completion, leaving the copy-capture frame unfinished and potentially stalling the capture session.

**Triggers:** When copying the rendered buffer fails, for example because of an unsupported format or renderer error.

**Suggested fix:** Call `wlr_ext_image_copy_capture_frame_v1_fail` in the `!success` branch, using the appropriate failure reason.
</issue_to_address>

### Comment 3
<location path="waylib/src/server/utils/wextimagecapturesourcev1impl.cpp" line_range="190-221" />
<code_context>
     // 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
</code_context>
<issue_to_address>
**issue (bug_risk):** `start()` sets `m_capturing` to true before validating that a render window exists, then returns without resetting it when `renderWindow()` is null. The source is left in a capturing state without render connections or a renderer, and a later start/request path cannot reliably recover because the source now appears already active.

**Triggers:** When the surface item is detached from its window or its render window is unavailable during start.

**Suggested fix:** Validate `renderWindow()` before setting `m_capturing`, or reset `m_capturing` before returning on startup failure.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread waylib/src/server/qtquick/private/wbufferrenderer.cpp Outdated
Comment thread waylib/src/server/utils/wextimagecapturesourcev1impl.cpp
Comment thread waylib/src/server/utils/wextimagecapturesourcev1impl.cpp
@glyvut
glyvut force-pushed the refactor/rec branch 3 times, most recently from b40e07d to 412cfc8 Compare August 27, 2026 11:10
@glyvut
glyvut requested a review from zccrs August 27, 2026 12:54
@deepin-bot

deepin-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 0.9.1
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #1348

@glyvut
glyvut requested a review from zzxyb August 28, 2026 06:13
Comment thread waylib/src/server/qtquick/private/wbufferrenderer.cpp
Comment thread waylib/src/server/utils/wextimagecapturesourcev1impl.cpp Outdated
Comment thread waylib/src/server/utils/wextimagecapturesourcev1impl.cpp
Comment thread waylib/src/server/utils/wextimagecapturesourcev1impl.cpp
Rework WExtImageCaptureSourceV1Impl to capture the entire surface item
subtree (content and subsurfaces) by offscreen rendering, instead of
copying only the surface content texture.

重构 ext-image-capture 的窗口截图,将完整窗口(含子表面)离屏渲染到缓冲,替代原有的表面纹理拷贝。

- Add WBufferRenderer::renderTransientItem to render an item subtree by
  temporarily re-parenting under a transient root node.
- Add WOutputRenderWindow::renderItemToBuffer for offscreen item rendering.
- Render offscreen buffer in afterRendering, emit frame in renderEnd, and
  copy the locked buffer in copy_frame.

Log: ext-image-capture 窗口截图改为离屏渲染完整窗口
PMS: BUG-374941, BUG-372395
Influence: 窗口截图改为渲染完整窗口(含子表面),不再局限于表面纹理拷贝。
@glyvut
glyvut requested a review from zzxyb September 1, 2026 08:46
@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: glyvut, zzxyb

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants