https://bugs.kde.org/show_bug.cgi?id=525047
Bug ID: 525047
Summary: KMag full frame buffer copy causing slow downs on
integrated gpu
Classification: Plasma
Product: kwin
Version First 6.7.3
Reported In:
Platform: NixOS
OS: Linux
Status: REPORTED
Severity: normal
Priority: NOR
Component: compositing
Assignee: [email protected]
Reporter: [email protected]
Target Milestone: ---
Created attachment 195640
--> https://bugs.kde.org/attachment.cgi?id=195640&action=edit
potential patch addressing, if not fixing, the issue
DESCRIPTION
The kmag zoom effect renders the full output into an offscreen texture every
frame, then samples that texture through the zoom transform. At zoom level Z
only 1/Z^2 of the texture is ever read, but the whole thing is still written
and read back each frame.
On integrated GPUs there is no VRAM, so that traffic crosses system memory
(here LPDDR5x) with the IOMMU in the path. The result is that enabling the
magnifier costs an order of magnitude more GPU power than compositing normally,
with the render engine essentially idle -- the cost is memory bandwidth, not
shading. It is much more noticeable on high-refresh outputs because the traffic
is per-frame.
3440x1440 at 240Hz, RGBA8: the offscreen texture is 19.8 MB, so write plus
readback is ~39.6 MB/frame, ~9.5 GB/s. At zoom 2.07 only 4.6 MB of that write
is ever sampled; at zoom 4.3 only 1.1 MB.
STEPS TO REPRODUCE
1. Connect a high-refresh display (3440x1440@240Hz here) on a system with an
integrated Intel GPU.
2. Measure GPU power and per-engine busy with `intel_gpu_top` while generating
continuous full-screen damage (e.g. a terminal printing output, or scrolling a
web page).
3. Enable the zoom effect (Meta++) and repeat the measurement at the same
damage rate.
OBSERVED RESULT
Zoom raises GPU power by ~10x while the render engine stays idle, and the
penalty scales linearly with refresh rate. Constant damage workload, zoom level
as the only variable:
refresh zoom=1.0 zoom=2.07 penalty
240 Hz 1.46 W / 590 MHz 14.62 W / 1810 MHz +13.2 W
120 Hz 0.82 W / 336 MHz 7.80 W / 1470 MHz +7.0 W
60 Hz 0.43 W / 209 MHz 2.96 W / 529 MHz +2.5 W
RCS (render engine) stayed at 0.0-0.1% in every one of those measurements.
Halving the refresh rate halves the penalty, twice over, which is what
identifies this as per-frame framebuffer traffic rather than shader work.
The cost is also effectively independent of zoom level -- 2.07x, 4.3x and 8.9x
all sit at ~1900-2200 MHz -- consistent with the offscreen texture being
full-output-sized regardless of how much of it is sampled.
Interactively this shows up as choppy scrolling whenever the magnifier is
active. The same magnification on a discrete GPU is not noticeably expensive,
since that composites in local VRAM without the IOMMU.
EXPECTED RESULT
Magnifying should not multiply per-frame memory traffic by the full output
size. Cost should track the region actually displayed.
SOFTWARE/OS VERSIONS
Operating System: NixOS 26.11 (Zokor), kernel 7.1.5
KDE Plasma Version: 6.7.3
KDE Frameworks Version: 6.28.0
Qt Version: 6.11.1
Graphics: Intel Core Ultra 7 265H (Arrow Lake-H), Xe-LPG integrated
Driver: i915, Mesa 26.1.6, "Mesa Intel(R) Graphics (ARL)"
Session: Wayland (kwin_wayland)
Outputs: DP [email protected] (scale 1.3) + eDP 1920x1200@60
ADDITIONAL INFORMATION
The relevant code is ZoomEffect::ensureOffscreenData(), which sizes the texture
from viewport.deviceSize() -- the whole output -- with no reference to m_zoom:
const QSize nativeSize = viewport.deviceSize();
and ZoomEffect::paintScreen(), which renders the entire scene into it:
RenderViewport offscreenViewport(viewport.renderRect(), ...);
GLFramebuffer::pushFramebuffer(offscreenData->framebuffer.get());
effects->paintScreen(offscreenRenderTarget, offscreenViewport, mask,
deviceRegion, screen);
Note also that ZoomEffect derives from Effect rather than OffscreenEffect,
so it inherits the base Effect::blocksDirectScanout() == true. Zoom therefore
disables direct scanout for the output whenever it is active, on top of the
offscreen pass. At zoom 1.0 isActive() is already false so nothing is lost
there, but it means any active zoom forces full composition.
Attached patch
(0001-effects-zoom-only-render-the-visible-region-offscree.patch) sizes the
offscreen texture to the region that survives the zoom transform and renders
only that, against master (418e7f64a7). It is a bandwidth reduction only -- it
does not restore direct scanout, so the remaining composition cost stays.
Caveats I have not resolved, and where review would help most:
- The source rect is derived from m_xTranslation/m_yTranslation and m_zoom,
which prePaintScreen() computes for the cursor/focus position. I believe that
is the same region the transform samples, but I am not confident the rounding
matches exactly at fractional scale (this output is scale 1.3), and a
half-pixel disagreement would show as a sampling artifact at the edges rather
than an obvious failure.
- The texture is now reallocated whenever the source rect size changes, so it
churns during zoom animation instead of being allocated once. Clamping or
rounding the size to reduce reallocation is probably wanted.
- m_offscreenData is keyed per logical screen and the existing TODO notes it
should be per view; I have not changed that, and I have only tested the
single-zoomed-output case.
- I have not verified behaviour with the pixel-grid upscaler path
(PixelGridZoom) or with HDR/non-sRGB color descriptions.
A larger fix would be to use a hardware scaling plane for magnification.
DrmPlane::set() already takes independent src and dst rects, so the property
plumbing exists, but I could not find any capability detection (SCALING_FILTER
support, permitted scale ratios) or any caller passing mismatched rects, so
that looks like a much bigger change than this one and I have not attempted it.
I know kde uses gitlab for patch submissions, but this is not by any means a
production ready patch, so I figured I would attach it here in case it is
useful for anyone and elicits comments.
Thank you!
--
You are receiving this mail because:
You are watching all bug changes.