KWin-native game upscaling for KDE Plasma — designed for launching games normally, including from Steam, without wrapping them in gamescope.
kwin-effect-upscale is an effect plugin for KWin, the compositor KDE Plasma
already runs. It is loaded by that compositor and does its work inside it:
there is no program to start by hand and nothing wrapped around the game.
Installing the package is the whole of the setup, and the effect is enabled
once it is installed. For X11 games the package also adds a session proxy in
front of Xwayland, which KWin starts from the next login on; see
games running through Xwayland.
Warning
Working alpha — it works, it is not finished. The effect can make a game render at a lower resolution and upscale the result to the physical display. On real hardware, SuperTuxKart produced up to 87% more frames per second with a smaller render target while KWin still presented the result on the same 3840 x 2160 display. See Measured.
Image quality, HDR, VRR and broad game compatibility still require more real-world testing, and its settings may still change.
This project brings game upscaling to KDE: games can render at a lower resolution for better performance while still filling your display at its native resolution, such as 4K — similar to the upscaling experience Windows gamers are used to, but integrated directly into KWin.
KDE Plasma is my desktop of choice, and I also use my PC for gaming with Steam. A common solution for game upscaling on Linux is to run the game through gamescope. Gamescope is an excellent project and an important technical reference for this one, but using it for scaling also means putting another compositor into the game path.
That raised a simple question:
If KWin is already compositing the desktop, why can't KWin upscale the game itself?
kwin-effect-upscale explores that idea.
Instead of wrapping a game in another compositor, the game stays on the normal Plasma desktop. KWin identifies configured applications, tries to obtain a smaller game buffer where possible, then scales that image to the physical output.
The point is not to reimplement gamescope feature for feature. The goal is a KDE-native gaming path where spatial upscaling feels like part of the desktop rather than a separate launch environment.
Eventually the normal workflow should be:
- Install the matching package.
- Start the game normally — including directly from Steam.
- Play.
The effect should take care of the rest. It is enabled by the package, the profiles it ships recognise a game and say how to ask it for a smaller image, and the global Quality default decides how much smaller, so there is no preset to choose and no configuration to write before it does anything.
There is plenty to tune for those who want to: a global preset, your own profile for a game the package does not yet know, scaling for every application rather than only the listed ones, a pixel threshold that decides which outputs are worth scaling at all, and a diagnostic display reporting what a game actually drew. These are power-user features — worth having, and never a step between installing the package and playing.
A maintained catalogue of well-known games should eventually provide tested settings for common combinations of game, hardware and display.
Installation and updates should be equally ordinary. The distribution package manager should handle dependencies and compatible versions, without hand-edited configuration files, manual dependency hunting or fragile launch recipes.
This is the destination, not the current state. The alpha already has editable application profiles and working resolution-control paths, but broad game compatibility and full real-device acceptance remain work to be done.
A 3840 x 2160 display contains four times as many pixels as 1920 x 1080. Rendering all of those pixels can become the limiting factor for a GPU.
If a game instead renders a smaller image, it may be able to produce frames faster. The image still has to fill the physical display, so it must be scaled back up. Basic stretching can look soft; a spatial upscaler tries to preserve edges and useful detail while enlarging the frame.
This project currently implements:
- FSR 1 / EASU for spatial upscaling;
- optional RCAS sharpening.
Upscaling is not free, but the extra GPU work can be much smaller than rendering the game at the display's native resolution.
It also cannot help every game. If the game is limited by the CPU, an internal frame cap, simulation work or something else unrelated to pixel rendering, lowering the render resolution may produce little or no frame-rate gain.
Because this effect works on the game's finished image, it scales the whole frame, including menus, text and HUD elements. An upscaler integrated directly into a game can instead upscale only the 3D scene and draw its user interface at native resolution.
Upscaling a game after it has already rendered a full-resolution frame does not save rendering work. By the time KWin receives a 3840 x 2160 frame, the game has already paid the cost of drawing those pixels.
For this effect to improve performance, two separate things have to happen:
- the game has to produce a smaller image;
- KWin has to upscale that image to the physical display.
The second step is the easy part. The first one depends on how the game talks to the desktop.
For a configured native Wayland application, the effect can advertise a different output mode to that application's Wayland connection.
For example, a game running on a physical 3840 x 2160 display can be presented with a 2560 x 1440 or 1920 x 1080 mode. Other applications continue to see the real display mode.
If the game follows the advertised mode, it renders the smaller image and KWin can upscale it.
Xwayland serves the session's X11 applications through one Wayland connection. An experimental session proxy gives selected X11 clients smaller display information before their first window. Games still launch normally; the effect resizes and presents their smaller buffers across the fullscreen output.
The proxy is installed with the effect, runs with the logged-in user's permissions and is switched on by default. Enable or disable it in the effect's settings, then log out and back in to change session routing. With either the effect or proxy disabled at login, stock Xwayland runs directly and no proxy process remains.
Early selection currently requires an explicit executable identity in the application catalogue and a single display at the desktop origin. On Linux, the proxy names a Wine or Proton program by its prefix and Windows program before its first display query; no shipped profile names one yet, and no Wine or Proton game has been accepted this way. Elsewhere no connection is identified. Native X11 input coverage still requires further acceptance testing; proxy routing alone does not establish that a game renders or receives input correctly.
The effect can provide a different rendering environment, but it cannot force a game's renderer to behave in a particular way.
A game may:
- follow the requested size;
- select another resolution;
- use its own internal render scale;
- impose its own frame-rate limit;
- ignore the request entirely.
If it continues rendering at the native output size, there is nothing useful for this effect to upscale.
That is why compatibility has to be established game by game.
Two consequences are worth knowing:
- A game may report a resolution you did not manually choose. Its settings show the mode it was offered, because from the game's point of view that is the display mode.
- Applications that are not configured are left alone, unless the All applications entry of the application list is checked. The effect ships a small set of known applications and allows additional profiles to be added.
Upscaling itself remains separate from resolution control: the effect can enlarge a smaller fullscreen image whether that size was requested by the effect or chosen by the application itself.
Steam is a primary target for this project. The intended user experience is that games can be started normally from Steam rather than through a special upscaling wrapper.
There is an important complication: Steam does not define the display path the game will actually use. Depending on the game and its runtime, it may reach KWin as a native Wayland client or through Xwayland, and bundled libraries can change which backends are available.
In particular, many Steam titles ship their own SDL rather than using the SDL provided by the Linux distribution. That means a backend switch that works for a distribution package is not automatically available to a Steam build of the same software.
For the current alpha, compatibility therefore means testing the actual game and confirming two things:
- whether the game really renders at the requested size;
- whether it reaches KWin through the display path we expect.
Broad Steam and Proton coverage is still future work. A maintained game catalogue is part of the long-term plan so that users should not have to reason about Wayland, Xwayland, SDL or individual engine behaviour themselves.
Working alpha. FSR 1 and optional RCAS are implemented, and the complete path has been measured on a physical display. SuperTuxKart was asked for a smaller buffer, supplied it, and the effect upscaled it to a 3840 x 2160 output while the game's own frame production increased substantially.
Implemented today:
- FSR 1 / EASU upscaling;
- optional RCAS sharpening;
- global scaling presets;
- editable application profiles;
- targeted native Wayland resolution requests;
- Xwayland resolution handling;
- requested-resolution versus supplied-buffer tracking;
- an experimental session proxy that gives selected X11 clients a smaller screen before their first window;
- selected borderless-window handling when content covers one output, or the smaller screen the proxy gave that program;
- a profile setting, in the configuration file only, that keeps an X11 game's resizing to the primary display;
- configurable global output threshold and per-application overrides;
- a Native resolution that asks a game for nothing smaller, while a buffer that arrives smaller anyway is still upscaled with FSR.
By default, outputs at or below 2,073,600 physical pixels (Full HD) bypass upscaling.
Verified so far:
- a native Wayland application can be given a smaller rendering target;
- KWin receives the smaller image;
- the effect upscales it to a physical 3840 x 2160 display;
- the Xwayland path works end to end with Extreme Tux Racer;
- virtual sessions verify resolution changes and client isolation.
Still requiring broader real-world acceptance:
- image quality;
- HDR;
- VRR;
- HDR and VRR together;
- physical input behaviour;
- more GPUs and displays;
- televisions;
- more native Wayland games;
- more Xwayland games;
- Steam titles;
- Proton/Wine titles.
Performance numbers below measure frame production, not image quality, power consumption or representative performance across games.
The developer handbook defines the remaining acceptance criteria in detail.
SuperTuxKart was tested on an NVIDIA workstation with a 3840 x 2160 output at
240 Hz, Wayland, KWin 6.3.6 and effect build
0.1.0+git20260920.5666b9422b. The machine was otherwise idle at a load of
0.23. Each row contains 30 samples taken over 60 seconds after a 10-second
warm-up.
| Preset | Game renders at | Upscaled | Game's own frames | Presented |
|---|---|---|---|---|
| native | 3840 x 2160 | no | 491.8/s | 236.8/s |
| quality | 2560 x 1440 | yes | 834.7/s | 237.1/s |
| performance | 1920 x 1080 | yes | 919.1/s | 237.3/s |
Read Game's own frames, not Presented. The presented rate is pinned near the display refresh rate in all three runs. What changes is how quickly the game itself can produce frames.
At performance, SuperTuxKart produced 1.87 times as many frames as at
native resolution while KWin still filled the same 4K display.
Three runs taken hours apart on different builds agreed within a few per cent:
- 492.6 / 833.3 / 949.4;
- 489.6 / 835.2 / 917.6;
- 491.8 / 834.7 / 919.1.
That makes the result useful as evidence that the mechanism works on this machine. It is still one game, one machine and one test environment rather than a promise about another system.
The returns also diminish. quality renders about 44% of the native pixel
count and gains about 70%; performance renders 25% of the native pixel count
and gains about 87%. Cutting the pixel count almost in half again therefore
buys relatively little additional frame production on this machine. Below that
point, something other than pixel rendering is becoming the limiting factor.
Extreme Tux Racer shows the other side of the same result. It supplied all three requested sizes — 3840 x 2160, 2560 x 1440 and 1920 x 1080 — and the latter two were upscaled to the physical display through the Xwayland path. Its frame rate remained 59.8/s in every run because the game is limited to 60 FPS. A game already at its own cap has nothing to gain from reducing rendering work.
One additional cost is not represented in either measurement: while the effect is active, it blocks direct scanout, so a game that could otherwise bypass composition no longer does so.
The effect runs inside KWin, so what it needs is a session that has one and a graphics stack that can do the arithmetic. The package enforces KWin binary compatibility at installation; the effect checks the graphics requirements at runtime, on the machine it is running on. When the answer is no, the effect is either never loaded or leaves that frame to KWin's ordinary rendering — it does not guess, and it does not degrade the image to fit. There is no GPU vendor list either. FSR 1 is arithmetic any conforming implementation runs, so AMD, Intel and NVIDIA are asked the same questions and answer for themselves.
| What has to be there | Why, and what happens without it |
|---|---|
| A Plasma Wayland session, KWin 6.3.6 or newer | The effect is a KWin plugin loaded by the running compositor; there is nothing else to start. Games inside that session may be native Wayland or Xwayland clients. A separate X11 desktop session is not a target and is untested. |
| The KWin the package was built against | A KWin effect is a compositor plugin and follows KWin's effect ABI. Each package depends on the exact KWin it was built with and refuses to install against another, so a KWin upgrade needs the matching build. |
| KWin's OpenGL compositing, which is the default | The scaling happens in shaders. Under the software renderer the effect reports itself unsupported and KWin never loads it. |
| OpenGL 3.1, or OpenGL ES 3.0 | That is what supplies GLSL 1.40 and GLSL ES 3.00, the languages the shaders are written in. Checked before the effect loads; below it, the effect is not offered at all. |
| High-precision floats in fragment shaders, on OpenGL ES | GLSL ES makes highp optional in a fragment shader, and medium precision can neither address a 4K pixel grid nor sample HDR without losing detail. Where the implementation does not offer it, the effect stays unloaded rather than filtering badly. |
| Rendering into a 10-bit-per-channel texture, and into a 32-bit float one for a linear destination | The filter needs somewhere to put the captured frame, and a linear destination carries values outside zero to one that only floating point holds. The texture is allocated and its framebuffer checked for completeness; a failure returns the effect to ordinary rendering until it is reconfigured. |
| A largest texture size covering the buffer and the output | GL_MAX_TEXTURE_SIZE is read from the driver, not assumed from the screen. Anything larger fails the allocation rather than being silently cropped, and the effect returns to ordinary rendering until it is reconfigured. |
| Colour handling the shaders decode | The output's transfer function has to be sRGB, gamma 2.2, PQ or linear, with finite, ordered luminances. Any other one refuses that window by name, and the window can be tried again after an output or colour change. |
Nothing else is needed beside the package: no Vulkan, no particular driver, no gamescope and no launcher wrapper. The one process the package adds is the X11 session proxy, which KWin starts in place of Xwayland.
While the effect is scaling a window it holds one texture the size of the game's buffer, and with sharpening on a second the size of the output — about 33 MB per texture at 3840 x 2160, or four times that per texture where the destination is linear. Switching sharpening off releases the larger one.
The developer handbook lists every limit the effect asks about, how it asks, and what it does with a refusal.
One row per distribution, and the package a person installs. These file names never change: they carry neither the version nor the distribution's release, so a bookmark keeps working across both. The release itself also carries the versioned packages, debug symbols, source packages and build records, which is what the longer list further down describes.
| Distribution | Architecture | Latest release | Nightly |
|---|---|---|---|
| Debian Trixie | amd64 | .deb | .deb |
| Debian Trixie | arm64 | .deb | .deb |
| Kubuntu 26.04 LTS | amd64 | .deb | .deb |
| Kubuntu 26.04 LTS | arm64 | .deb | .deb |
| Fedora | x86_64 | .rpm | .rpm |
| Fedora | aarch64 | .rpm | .rpm |
| openSUSE Tumbleweed | x86_64 | .rpm | .rpm |
| openSUSE Tumbleweed | aarch64 | .rpm | .rpm |
| Arch | x86_64 | .pkg.tar.zst | .pkg.tar.zst |
| FreeBSD | amd64 | .pkg | .pkg |
These are development artifacts, not a stable release. The alpha warning at the top of this document also applies to packaged builds.
Packages are built for:
| Distribution | Architectures | Binary | Debug symbols | Source |
|---|---|---|---|---|
| Debian Trixie | amd64, arm64 | .deb |
-dbgsym |
.dsc + .tar.xz |
| Kubuntu 26.04 LTS | amd64, arm64 | .deb |
-dbgsym .ddeb |
.dsc + .tar.xz |
| Fedora | x86_64, aarch64 | .rpm |
-debuginfo, -debugsource |
.src.rpm |
| openSUSE Tumbleweed | x86_64, aarch64 | .rpm |
-debuginfo, -debugsource |
.src.rpm |
| Arch | x86_64 | .pkg.tar.zst |
-debug |
.src.tar.gz |
| FreeBSD | amd64 | .pkg |
- | - |
Each distribution gets what its own packaging expects: the binary, its debug symbols, and the source the binary was built from. Arch is x86_64 alone because Arch itself supports one architecture — its ARM port is a separate distribution with its own repositories, not an architecture of this one.
Important
Only Debian Trixie is tested on real hardware. That is the one distribution with an acceptance machine behind it, so it is the only one where the effect has been run against a physical display and a real game. Every other package is built and checked in a clean container of its own distribution, FreeBSD's in a virtual machine — it compiles, it installs, and its plugin loads — which is not the same as having been used. Treat the others as untested builds until that changes.
Pick the package matching your distribution because a KWin effect is built against the KWin version it is loaded into.
- Releases: https://github.com/JensKSP/kwin-effect-upscale/releases/latest
- Nightly: https://github.com/JensKSP/kwin-effect-upscale/releases/tag/nightly
The nightly release is rebuilt once a day from master, when master has
moved since the last nightly.
Install a downloaded package with your distribution's own tool:
sudo apt install ./kwin-effect-upscale_<version>.<distribution>_<architecture>.deb
sudo dnf install ./kwin-effect-upscale-<version>-1.fc43.x86_64.rpm
sudo zypper install ./kwin-effect-upscale-<version>-1.x86_64.rpm
sudo pacman -U ./kwin-effect-upscale-<version>-1-x86_64.pkg.tar.zst
sudo pkg add ./kwin-effect-upscale-<version>-amd64.pkgEach package depends on the exact KWin it was built against, so it refuses to install against a different one rather than letting the compositor load a plugin built for another ABI. After a KWin upgrade, take the matching build.
Every release also carries the source tarball with its SHA-256 checksum. The Debian packages each have a debug-symbol package beside them; the Fedora, openSUSE and Arch builds carry whatever debug sidecar their own distribution generates, which is not guaranteed to be present.
Release assets are covered by a keyless GitHub build attestation, so a download can be traced back to the workflow and commit that produced it:
gh attestation verify ./kwin-effect-upscale_<version>.<distribution>_<architecture>.deb \
--repo JensKSP/kwin-effect-upscaleSHA256SUMS lists every asset in the release. provenance.sigstore.json holds
the signing bundle and is verified separately, so it is not itself listed there.
kwin-effect-upscale-<version>.spdx.json is the release's software bill of
materials in SPDX 2.3: every asset with its checksum, the AMD shader code the
packages contain, and each Debian package's runtime and build dependencies. It
is listed in SHA256SUMS and attested like the packages.
The package version uses a tilde before the distribution suffix, as Debian expects, but GitHub release assets cannot preserve that tilde. For example,
0.1.0+git20260917.3d2d99e6a0.trixie_amd64.deb
installs as:
0.1.0+git20260917.3d2d99e6a0~trixie
The package enables the effect, so there is nothing to switch on. To confirm it is loaded in the running Plasma session:
qdbus6 org.kde.KWin /Effects org.kde.kwin.Effects.isEffectLoaded upscaleA freshly installed plugin is normally picked up immediately. If that reports
false, log out and back in.
It appears as Upscale under Appearance in System Settings → Desktop Effects, which is also where it is switched off again. From a shell, the equivalent of that tick is:
kwriteconfig6 --file kwinrc --group Plugins --key upscaleEnabled true
qdbus6 org.kde.KWin /Effects org.kde.kwin.Effects.loadEffect upscaleThe plugin identifies itself each time KWin loads it, so the newest such line in the journal names the build loaded last in the current session:
journalctl --user -b -u plasma-kwin_wayland -r -g 'upscale [0-9]+\.[0-9]+\.[0-9]+' | head -1
# upscale 0.1.0+git20260917.ed8f450b4e (branch master), built 2026-09-17T20:50:02Z, Qt 6.8.2A version without a +git suffix is a release. Other versions identify the Git
commit they were built from, and -dirty means a development build contained
uncommitted changes. Packaged builds report the complete package version,
including the distribution suffix. Nightly source archives retain their
snapshot version without Git.
These are what it takes to compile the effect; System requirements is what it takes to run it.
The effect is built against the KWin installed on the machine and loaded into it, so the development files must belong to the KWin version that will actually run the plugin.
The build requires:
- C++23;
- CMake 3.24;
- Qt 6.8;
- KDE Frameworks / ECM 6.13;
- development files for the target KWin.
debian/control is the authoritative build-dependency list, including build,
tool and test dependencies.
On Debian and Kubuntu, install them with mk-build-deps from devscripts
(using equivs to create the dependency package):
sudo mk-build-deps --install --remove debian/controlThe maintained containers install dependencies from the same file. A cached
image does not update itself, so each one records the debian/control it was
built from and the checks refuse to run when the two have diverged, naming the
rebuild rather than failing later on a missing header:
podman build --pull --build-arg DEPENDENCY_EPOCH="$(date -u +%Y-%m-%d)" \
-t upscale-check:trixie -f containers/trixie/Containerfile .DEPENDENCY_EPOCH is what refreshes the installed packages: it invalidates the
layer that runs apt-get, so passing today's date picks up current packages
from an otherwise unchanged Containerfile. CI passes the same value.
Other distributions provide the same components under their own package names.
The CMake package names to look for are ECM, Qt6, KF6 (Config,
CoreAddons, I18n, and KCMUtils for the settings module), KWin, Libdrm and
XCB (XCB, RANDR). The X11 session proxy also needs Xwayland installed in
/usr/bin or /usr/local/bin when the build is configured.
git clone https://github.com/JensKSP/kwin-effect-upscale.git
cd kwin-effect-upscalecmake -B build -S . -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build buildNinja uses its native parallelism. Set CMAKE_BUILD_PARALLEL_LEVEL if a machine
needs a lower job limit.
In-source builds are refused. Without -DCMAKE_BUILD_TYPE, the project
configures a debug build, which is not what you want for playing games.
sudo cmake --install buildWith KWIN_BUILD_KCMS=ON (the default), this installs the effect, its
configuration module, the X11 proxy and the Plasma session hook:
<prefix>/lib/<multiarch>/qt6/plugins/kwin/effects/plugins/upscale.so
<prefix>/lib/<multiarch>/qt6/plugins/kwin/effects/configs/kwin_upscale_config.so
<sysconfdir>/xdg/kwinupscalerc
<libexecdir>/kwin-upscale-x11/Xwayland
<sysconfdir>/xdg/plasma-workspace/env/kwin-upscale-x11.sh
kwinupscalerc holds the effect's own defaults and lands in KDE's
configuration directory, which is /etc/xdg for the default /usr prefix. A
user's own changes go to a file of the same name in their configuration
directory, which KConfig layers over this one.
The install prefix defaults to the one KDE Frameworks uses. On Debian that is
/usr, which is also where Qt and KWin look for plugins.
If you install to another prefix, KWin will not find the plugin unless the
session that starts kwin_wayland has QT_PLUGIN_PATH pointing at:
<prefix>/lib/<multiarch>/qt6/plugins
The effect's defaults and the X11 session hook land in <prefix>/etc/xdg, which
is read only when the session's XDG_CONFIG_DIRS includes it; without it the
shipped profiles are missing and the proxy is never put in front of
Xwayland.
Close the game, install the new build, then run:
python3 -B tools/reload-upscale.pyTo add Reload Upscale to KDE's application menu and desktop:
python3 -B tools/reload-upscale.py --install-launcherKeep this checkout at the same path. KDE may ask you to trust the desktop
launcher on its first use. Python 3, qdbus6 (or qdbus-qt6), qtpaths6,
sudo, and, for the icon, kdialog and pkexec are required. The installer
also uses xdg-user-dir. For a custom installation, append
--plugin /path/to/kwin/effects/plugins/upscale.so to either command.
The tool reloads the installed binary and displays its running build identity;
it does not build or install a new version. Administrative authentication may
be requested to copy and remove a temporary plugin file. KWin and applications
keep running. The last successful result is in build/reload-upscale/latest.log.
This is a development shortcut: Qt can keep an unloaded library in memory, so the tool loads a copy under a fresh temporary effect name. The normal settings page may therefore report Upscale as unloaded, and its Apply button addresses the normal name. After saving settings, use Reload Upscale again; do not enable another instance alongside the temporary one. Temporary discovery files are removed after loading, so the next login uses the normal installed plugin. Old libraries can remain in memory until logout. A fresh session is still the final check for normal installation and settings-page behavior.
Remove org.kde.upscale.reload.desktop from your desktop and
~/.local/share/applications/ to uninstall the launcher (use your
XDG_DATA_HOME/applications/ directory if customized).
sudo xargs rm -v < build/install_manifest.txtThe permanent developer handbook contains the project requirements, specification and design, including implemented behaviour and open acceptance criteria.
HDR and variable refresh rate are project requirements, including using both at the same time while upscaling. Real-device acceptance for those paths remains open.
This is an independent project and not an official KDE project.
A useful test application can run fullscreen, can render below the physical output resolution, and lets us identify which display path it takes.
Debian's SDL2 provides a Wayland backend, so applications linked against the
system SDL2 can often be sent through either backend using SDL_VIDEODRIVER.
Applications that bundle their own SDL2 — as many Steam titles do — remain
limited by the backends in that bundled copy.
Extreme Tux Racer and SuperTuxKart are used for resolution-request testing and ship with measured profiles. The other entries below are candidates and test tools, not compatibility claims.
| Application | Where it comes from | Display path | Graphics API |
|---|---|---|---|
| Extreme Tux Racer | extremetuxracer |
X11 through Xwayland only (SFML) | OpenGL |
| SuperTuxKart | supertuxkart |
either | OpenGL or Vulkan |
| Taisei | taisei |
either | OpenGL |
| 0 A.D. | 0ad |
either | Vulkan or OpenGL |
| OpenArena on ioquake3 | openarena, ioquake3 |
either | OpenGL |
| Warzone 2100 | warzone2100 |
either | OpenGL |
| Unvanquished | own launcher, or Flathub | Wayland without a switch (SDL 3) | OpenGL |
| Veloren | Airshipper, or Flathub | Wayland without a switch (winit) | Vulkan, through wgpu |
SuperTuxKart accepts both display backend and resolution on the command line, which makes the intended test case easy to express:
SDL_VIDEODRIVER=wayland supertuxkart --fullscreen --screensize=1280x7200 A.D. is interesting because Alpha 27 can render through Vulkan and includes its own FSR implementation, allowing the same scene to be compared with this effect. Veloren is a useful native Wayland plus Vulkan candidate and also has an internal render scale.
Left 4 Dead 2 is the native Source engine title this effect's X11 path was
developed against, launched normally from Steam through pressure-vessel. Its
window identifies itself as hl2_linux, which is the engine binary rather than
the game, so the profile the package ships recognizes it by that window
together with the folder its program is in, Left 4 Dead 2/hl2_linux. Whether
that program path resolves for a game running inside pressure-vessel has not
yet been observed; where it does not, the profile does not match.
Source takes its fullscreen size from the window manager and never asks
Xwayland for a mode, which is the case the effect has to present and map
pointer input for itself.
Project Zomboid is not open source, but it has been useful for observing an
X11/Xwayland game path. Its LWJGL 2 compatibility layer pins GLFW to X11 unless
the system property zomboid.wayland=1 is set, even though the bundled GLFW
contains both backends. In the observed X11 session, the process mapped
libX11 and libGLX, not libwayland-client, and its log showed the XRandR
mode request that Xwayland then emulated.
For any candidate, the display path still has to be verified rather than
assumed. Two useful observations are whether the process has
libwayland-client mapped and whether its window appears in Xwayland's window
tree.
The design draws on existing free software and published shader implementations:
- gamescope demonstrates compositor-level game scaling with separate handling of overlays, sharpening and output colour management. Its FSR, NIS, SGSR and pixel-filter paths are important references for this effect.
- AMD FidelityFX Super Resolution 1 provides the EASU upscaler and RCAS sharpening pass.
- AMD FidelityFX CAS provides another approach to adaptive sharpening with optional upscaling.
- NVIDIA Image Scaling combines spatial upscaling and adaptive sharpening and documents requirements for SDR and HDR input.
- Snapdragon Game Super Resolution 1 provides a spatial filter that combines upscaling and sharpening in one shader pass, including a GLSL reference implementation.
- libplacebo provides references for bicubic and Lanczos filters, including EWA variants and anti-ringing.
- KWin's own effects guide the plugin structure and integration. The zoom effect's xBRZ shader is also a reference for enlarging pixel graphics.
Anime4K, FSRCNNX and RAVU have also been considered as additional spatial alternatives.
The current effect implements FSR 1 with optional RCAS. Incorporated third-party code retains its own copyright and licence notices.
- Warnings are errors by default. Disable that for a distribution build with
-DCMAKE_COMPILE_WARNING_AS_ERROR=OFF. DESTDIRis honoured:DESTDIR=/tmp/stage cmake --install build.- The plugin declares the KWin version it was built against, so it must be rebuilt after a KWin upgrade.
- Debian packages depend on the exact
kwin-commonversion they were built against, so a KWin upgrade requires a matching rebuild of this package. debian/is part of the tree and builds a single binary package withdpkg-buildpackage -b.- The source format is native, so no orig tarball is required.
- Build dependencies live in
debian/control; CI installs them from that file withmk-build-depson the Debian family, andtools/distribution-packages.pytranslates it for the other distributions. - The build honours
SOURCE_DATE_EPOCH, which debhelper sets from the changelog, so packaged builds remain reproducible. No other part of the build reads the wall clock.
Contributions are welcome: bug reports, testing on different setups, documentation improvements and code. Feel free to open an issue or pull request on GitHub.
The contributor guide describes reporting, maintained build environments, checks and submission expectations.
We use Codex and Claude to help write code for this project. We aim to keep "AI slop" out: unnecessary abstractions, boilerplate and changes we cannot explain or verify. The standard is readable code that fits KWin's conventions, with human review and checks for correctness. Responsibility remains with us.
Documentation under doc/ is permanent and written for humans. For each major
implementation slice, coding agents keep one temporary working document under
doc/agents/ with the topic's start state, target state, scope,
dependencies, acceptance criteria, progress, findings, test results and
remaining work.
Once the implementation is complete and all required tests pass — including
real-device acceptance where required — the temporary document is removed.
Lasting requirements and design conclusions belong in permanent documentation;
implementation explanations belong in source comments. Source code, comments,
tests and human documentation together are the source of truth. AGENTS.md
instruction files remain permanent.
Pre-commit defines the repository checks. Install both hooks and run both stages:
pipx install pre-commit==4.6.2
pre-commit install --hook-type pre-commit --hook-type pre-push
pre-commit run --all-files
pre-commit run --all-files --hook-stage pre-pushInside the maintained container, this runs both stages exactly as CI does:
python3 -B tools/run-checks.py lintThe commit-stage checks focus on changed files. Pre-push runs whole-tree checks and regression tests. CI runs both over the repository and adds GCC and Clang builds, an arm64 build, clang-tidy, metadata-schema checks, coverage and sanitizers, and a Trixie package smoke test when a pull request touches the packaging inputs. Nightly additionally runs the full package matrix, the source-archive test and separate KWin-master compatibility builds. Documentation-only changes use the reduced checked path described in the contributor guide.
The checks cover KDE coding style through clang-format and KWin's own
.clang-format, CMake formatting and static checks, Markdown linting, spelling
in documentation and comments, REUSE compliance and source-file size limits.
The CMake linter also checks the plugin folder, with formatting rules disabled
there to preserve KWin's style. Gersemi formats only the surrounding project.
Tools under tools/ are predominantly Python. ruff lints and formats them
with all rules enabled, and mypy --strict checks their typing.
The source-file budget permits 400 code lines, with warnings above 300. Comments and blank lines are excluded; multiline strings such as embedded shaders count. Unreadable or unmeasurable files fail the check. Regression tests for these rules and the build metadata run in the pre-push stage.
clang-tidy requires a configured Clang build. In the maintained container:
python3 -B tools/run-checks.py tidyCI builds Debian Trixie, the minimum supported environment using KWin 6.3.6,
with both GCC and Clang and with warnings treated as errors. Nightly also builds
against KDE neon unstable, which tracks KWin master. Both environments live
under containers/ so the same builds can be reproduced locally.
Both images verify CMake, Ninja, GCC and Clang during creation. Ninja comes from
the shared debian/control dependencies. An existing local image does not
update itself, so each records the debian/control it installed and the checks
stop with a rebuild instruction when it no longer matches the tree. Dependabot
watches the base images of the Trixie, neon unstable and package containers and
the actions pinned in the workflows and in the repository's own composite
actions; it does not rebuild anything.
A release is created from a tag; nothing else is performed manually:
# the tag, project(VERSION) and debian/changelog must agree, or CI stops
git tag -a v0.4.0 -m 'kwin-effect-upscale 0.4.0'
git push origin v0.4.0The release workflow builds every package in the table above - Debian Trixie and Kubuntu 26.04 on amd64 and arm64, Fedora and openSUSE Tumbleweed on x86_64 and aarch64, and Arch on x86_64 - each with its debug symbols and the source package its own distribution expects, and FreeBSD on amd64, built in a virtual machine and published as the binary package alone. It creates the project's source tarball, checksums and attests the lot, and publishes it as a GitHub release whose notes name the file to download first.
nightly is one rolling pre-release, rebuilt once a day from master when
master has moved since the last one. Its tag is deleted and recreated each time, so it is not a stable URL
for a fixed build.
src/plugins/upscale/ the effect, laid out exactly as KWin lays out its own
src/x11proxy/ the X11 session proxy KWin starts in place of Xwayland
src/buildinfo/ the build identity compiled into the effect
autotests/ unit, render and nested-KWin integration tests
cmake/ stand-ins for KWin's in-tree build macros
containers/ build environments: Trixie minimum, KDE neon unstable,
the package builders and the conformance suites
debian/, packaging/ Debian, RPM, Arch and FreeBSD packaging
tools/ checks, test runners, measurement, packaging and release
.github/ CI, nightly and release workflows
doc/ permanent human documentation: what the effect does and why
doc/agents/ temporary implementation documents for coding agents
src/plugins/upscale/ is intended to remain copyable into KWin's own
src/plugins/ unchanged. Everything specific to building this effect outside
KWin lives elsewhere in the repository.
GPL-2.0-or-later, REUSE compliant; CI and tool configuration is CC0-1.0.
Third-party shaders and KWin's .clang-format keep their own licence (MIT).