Skip to content

perf(shot): reduce X11 screenshot startup overhead - #949

Merged
lzwind merged 2 commits into
masterfrom
perf/screenshot-startup-fastpath
Oct 20, 2026
Merged

lzwind merged 2 commits into
masterfrom
perf/screenshot-startup-fastpath

Conversation

@hualet

@hualet hualet commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Ordinary X11 screenshots perform millions of XGetPixel/setPixel calls while preparing the desktop background and synchronously spawn lscpu before showing the selection UI. This change reduces that startup work while preserving capture coordinates and the existing platform tuning parameters.

  • Convert native aligned 32-bit ZPixmap images in place after checking depth (24/32), RGB masks, byte order, stride and xoffset: force opaque alpha and hand the Xlib buffer to the QImage (no second full-screen allocation or copy). Every other layout keeps the previous XGetPixel conversion, which is no longer rejected by extra validation.
  • ximageconverter is built only for Qt6, its sole user.
  • Read vendor_id from /proc/cpuinfo for the existing CentaurHauls check instead of starting lscpu.
  • Add focused pixel/CPU regression tests and reproducible component and D-Bus launch benchmarks.

Local performance

X11, Qt6, 2880×1800 physical screen, DPR 1.75; same source baseline and Release options, 11 interleaved runs per variant, new process each time with warm filesystem cache:

Boundary Before median After median
Process launch → showFullScreen return 240.30 ms 184.63 ms (23.2% shorter)
D-Bus method entry → showFullScreen return 152.15 ms 95.57 ms (37.2% shorter)

The endpoint is the display call returning, not a frame presented by the compositor. Measurements exclude real keyboard dispatch and cold D-Bus activation, and use log receipt times. The benchmark scripts and workload details are in tests/startup/README.md.

Validation and scope

  • Real 2880×1800 desktop XImage conversion: 55.00 ms → 2.22 ms (31 alternating samples, identical pixels). XGetImage itself (~23 ms) is now the dominant capture cost.
  • Standalone CTest: 2/2 passed, including pixel equality, alpha, padding, endianness, alignment, depth/mask fallback, buffer ownership and CPU metadata cases. The native-layout accessor regression fails against the original conversion.
  • ASan/UBSan tests: 2/2 passed outside the ptrace-restricted sandbox. Non-PIE sanitizer test executables also passed 20 repetitions per test.
  • Before/after screenshot main program builds (qmake6 src/src.pro) succeeded; fullscreen-save smoke test produced a valid opaque 2880×1800 PNG.
  • The top-level project, Dock/pin subprojects and Debian package were not built. libdtk6tablerecognizer-dev remains unavailable for the pin-screenshots subproject. The full existing test suite, Qt5, multiscreen, Wayland/Treeland and manual selection/annotation workflows remain unverified.

Summary by Sourcery

Optimize X11 screenshot startup by eliminating avoidable image conversion and CPU detection overhead while preserving fallback behavior and capture correctness.

Enhancements:

  • Reduce Qt6 X11 screenshot startup overhead by reusing compatible native image buffers and avoiding unnecessary pixel-by-pixel conversion work.
  • Replace synchronous lscpu execution with direct /proc/cpuinfo vendor detection during startup.

Build:

  • Build the X11 image converter only for Qt6 and include shared CPU vendor parsing sources in supported build configurations.

Documentation:

  • Add usage and benchmark documentation for focused startup regression tests and launch measurements.

Tests:

  • Add focused regression coverage for X11 image conversion layouts, pixel preservation, buffer ownership, opacity, and CPU vendor parsing.
  • Add reproducible component and D-Bus startup benchmarks comparing legacy and optimized behavior.

@sourcery-ai

sourcery-ai Bot commented Oct 11, 2026

Copy link
Copy Markdown

Reviewer's Guide

Reduces X11 screenshot startup overhead by adding a validated scanline conversion path for native 32-bit images and replacing synchronous lscpu execution with direct /proc/cpuinfo parsing, while preserving fallback behavior and adding targeted regression tests plus reproducible component and D-Bus benchmarks.

Sequence diagram for optimized X11 screenshot startup

sequenceDiagram
    participant ScreenGrabber
    participant XServer
    participant XImageConverter
    participant QPixmap
    ScreenGrabber->>XServer: XGetImage()
    ScreenGrabber->>XImageConverter: toImage(ximg)
    alt validated native 32-bit ZPixmap layout
        XImageConverter->>XImageConverter: scanline memcpy and force opaque alpha
    else other XImage layout
        XImageConverter->>XImageConverter: XGetPixel() and setPixel()
    end
    XImageConverter-->>ScreenGrabber: owned QImage
    ScreenGrabber->>QPixmap: fromImage(qimg)
    ScreenGrabber->>XServer: XDestroyImage()
    ScreenGrabber->>XServer: XCloseDisplay()
Loading

Sequence diagram for direct CPU vendor detection

sequenceDiagram
    participant Utils
    participant ProcCpuInfo as /proc/cpuinfo
    participant CpuVendor
    Utils->>ProcCpuInfo: open(ReadOnly)
    Utils->>CpuVendor: isZhaoxin(cpuInfo)
    loop read lines
        CpuVendor->>ProcCpuInfo: readLine()
    end
    CpuVendor-->>Utils: vendor_id == CentaurHauls
    Note over Utils,ProcCpuInfo: No lscpu process is spawned
Loading

File-Level Changes

Change Details Files
Replace per-pixel X11 image conversion with a validated native-layout scanline fast path while retaining legacy fallback behavior.
  • Add an owned QImage converter for aligned 32-bit ZPixmap images with supported depth, masks, byte order, stride, and offset.
  • Force opaque alpha and reject invalid image metadata before conversion.
  • Keep XGetPixel/setPixel conversion for non-native or uncommon layouts.
  • Integrate conversion into X11 screenshot capture and handle conversion failure.
src/utils/ximageconverter.cpp
src/utils/ximageconverter.h
src/utils/screengrabber.cpp
src/CMakeLists.txt
src/src.pro
Eliminate synchronous lscpu startup work by parsing the CPU vendor directly from procfs.
  • Parse vendor_id records from a QIODevice with whitespace- and case-insensitive matching.
  • Use /proc/cpuinfo in the existing Zhaoxin detection path and preserve the CentaurHauls decision.
  • Update build integration and existing unit coverage for the parser.
src/utils/cpuvendor.cpp
src/utils/cpuvendor.h
src/utils.cpp
src/CMakeLists.txt
src/src.pro
tests/ut_screen_shot_recorder/ut_screen_shot_recorder.pro
tests/ut_screen_shot_recorder/ut_utils_cov2.h
Add focused regression coverage and reproducible startup benchmarks for pixel conversion and CPU detection.
  • Test pixel equality, alpha normalization, source ownership, alignment, padding, endianness, depth/mask fallback, and invalid inputs.
  • Test CPU metadata parsing with in-memory readable, empty, malformed, and misleading inputs.
  • Provide standalone CTest targets, legacy comparison mode, component timing, and D-Bus launch benchmarking documentation/scripts.
tests/startup/CMakeLists.txt
tests/startup/README.md
tests/startup/ximagefixture.h
tests/startup/test_startup.cpp
tests/startup/test_cpuvendor.cpp
tests/startup/benchmark_startup.cpp
tests/startup/benchmark_launch.py

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

@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 1 issue

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

## Individual Comments

### Comment 1
<location path="src/utils/ximageconverter.cpp" line_range="33-36" />
<code_context>
+
+    for (int y = 0; y < image->height; ++y) {
+        if (direct) {
+            const char *source = image->data + qsizetype(y) * image->bytes_per_line;
+            QRgb *destination = reinterpret_cast<QRgb *>(result.scanLine(y));
+            for (int x = 0; x < image->width; ++x) {
+                quint32 pixel;
+                // memcpy avoids aliasing assumptions about Xlib-owned storage.
+                std::memcpy(&pixel, source + qsizetype(x) * sizeof(pixel), sizeof(pixel));
</code_context>
<issue_to_address>
**Qt 5.6 builds fail**

When the project is built with Qt 5.6–5.9, the `qsizetype` casts in `ximageconverter.cpp` are undefined, so the converter does not compile and the application build fails.

Use an index type available in the supported Qt 5.6–5.9 builds for both pointer offsets.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src/utils/ximageconverter.cpp Outdated
Comment on lines +33 to +36
const char *source = image->data + qsizetype(y) * image->bytes_per_line;
QRgb *destination = reinterpret_cast<QRgb *>(result.scanLine(y));
for (int x = 0; x < image->width; ++x) {
quint32 pixel;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High · Qt 5.6 builds fail

When the project is built with Qt 5.6–5.9, the qsizetype casts in ximageconverter.cpp are undefined, so the converter does not compile and the application build fails.

Use an index type available in the supported Qt 5.6–5.9 builds for both pointer offsets.

Prompt for AI agents
In `src/utils/ximageconverter.cpp` at lines 33-36:

**Qt 5.6 builds fail**

When the project is built with Qt 5.6–5.9, the `qsizetype` casts in `ximageconverter.cpp` are undefined, so the converter does not compile and the application build fails.

Use an index type available in the supported Qt 5.6–5.9 builds for both pointer offsets.

Hualet Wang and others added 2 commits October 20, 2026 13:51
Convert supported native RGB32 XImages by scanline and read CPU vendor metadata directly from procfs. Add pixel regressions and reproducible startup benchmarks.

Log: Reduce startup overhead for X11 screenshots
- Convert native RGB32 XImages in place and hand the Xlib buffer to QImage,
  removing a full-screen allocation and copy.
- Relax validation so uncommon layouts take the legacy XGetPixel path
  instead of being rejected; write fallback pixels via scanLine.
- Build ximageconverter only for Qt6, its sole user.
- Update stale lscpu references in unit-test comments.

Co-Authored-By: Claude <noreply@anthropic.com>
@lzwind
lzwind force-pushed the perf/screenshot-startup-fastpath branch from 5697feb to 67c91ce Compare October 20, 2026 05:51
@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: hualet, lzwind

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

@lzwind
lzwind merged commit 3fcf87e into master Oct 20, 2026
6 of 10 checks passed
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