wayfire icon indicating copy to clipboard operation
wayfire copied to clipboard

Add an option in `core` that stops Wayfire from scaling XWayland applications

Open Morxemplum opened this issue 6 months ago • 9 comments

The Problem

Wayland has a notorious problem when it comes to XWayland and HiDPI monitors. My monitors are considered HiDPI, the UI is far too small for me to make any use out of it. But a scale factor of 2 makes the UI far too big to navigate, so I have to use fractional scaling with a scaling factor of 1.5. Since the X11 protocol has not been extended to include the concept of fractional scaling, this introduces a problem when trying to scale XWayland applications. Fractional values like 1.5 will be truncated to a value of 1, 66% of the value. Instead of XOrg seeing my monitor as 3840x2160, the highest value it can only see is 2560x1440, 66% of my monitor's size. The compositor trying to upscale the application is like trying to upscale a PNG image: it degrades pixel quality and leads to the infamous blurriness problem.

This has been discussed before in issues such as #599 and #1180. The problem with the pull request linked in the first issue is that discussion has unfortunately stalled, due to a lot of bureaucracy-related problems with the pull request (This is unsurprising as Wayland development is often criticized for bikeshedding and having absolute perfectionism). The AUR packages that were offered in the discussion are no longer available; even if you are on an Arch/Arch-based distro, this option is no longer available.

Then more discussion was picked up on this pull request on the Xorg side, along with this one. Similar to the wlroots pull request, discussion on them have completely stalled and gone nowhere. Unfortunately, this means that having a more ideal solution on the XOrg side and/or upstream isn't going to be arriving any time soon. This has lead Wayland compositors to take this matter into their own hands to provide more iterative, nonstandard solutions to address this issue with their users.

Wayfire does allow us to modify XOrg's settings before starting the XWayland server. Through xwayland_startup_script, you can run a shell script where you'd want to modify XOrg related settings. However, because the compositor itself is upscaling the XWayland client, there is no way to modify the X server to disable scaling. Even if I were to use cvt to try and calculate the Modeline values for 2160p, then use xrandr to create, insert, and set the output to follow that mode, XOrg errors and refuses to accept that mode for the output.

X Error of failed request:  BadValue (integer parameter out of range for operation)
  Major opcode of failed request:  140 (RANDR)
  Minor opcode of failed request:  21 (RRSetCrtcConfig)
  Value in failed request:  0xa00
  Serial number of failed request:  28
  Current serial number in output stream:  28

The Approaches

There are a couple different methods that Wayland compositors are doing to address the pixel quality issue on fractionally scaled HiDPI monitors.

KDE's KWin, through Plasma, offers their desktop users two options: Scaled by the system, which is the current method that leads to the blurriness problem; and Apply scaling themselves, which defers the responsibility of scaling from the compositor to the application itself. This tends to work on modern XWayland applications, as they will scale their UI accordingly. The downside is that on (usually older) applications, they either rely on the user to scale the UI manually, or they outright don't have any code to rescale the UI; this leads to an unscaled effect on those applications. Hyprland has an explicit property called force_zero_scaling that does this same approach.

GNOME's Mutter, offers two experimental flags "scale-monitor-framebuffers" and "xwayland-native-scaling" (introduced in 47) to handle the issue. What they do is take the current XOrg coordinate space and they multiply it by a rounded up value of your highest scale factor into a staging coordinate space. Then the resulting image is achieved by downscaling. So instead of 1440p, I'd have 2880p (2 / 1.5 would give me the multiplication factor to my resolution to achieve this). This solution works great in ensuring great pixel quality among all XWayland applications. However, because it is rendering at a resolution that is greater than my monitor, that is going to increase GPU usage and lead to a performance hit in applications where performance is crucial (e.g. gaming).

The Solution

I personally think that the first solution is probably the simpler, and better intermediary option until there's improvements on XOrg's end. Considering that Wayfire's objective is to be lightweight and be performant, Mutter's approach goes against that philosophy. I think that adding an option under the [core] category would be the best way of going about this, called xwayland_auto_scaling. It would be a boolean value that would determine whether Wayfire should upscale the applications like any other client, or force XWayland to have a static scale of 1 and defer scaling to the applications. And if additional tinkering needs to be done to try and fix any scaling issues, hopefully they can be done purely on the XOrg side through xwayland_startup_script.

Morxemplum avatar Jul 10 '25 03:07 Morxemplum

The approaches you mention require wlroots changes as well. This is why you don't see any of the compositors using wlroots which have these workarounds :)

I would recommend using patched Xwayland and wlroots for now.

ammen99 avatar Jul 10 '25 09:07 ammen99

The approaches you mention require wlroots changes as well. This is why you don't see any of the compositors using wlroots which have these workarounds :)

Hmmmmm. It seems like Hyprland used to be wlroots-based but not anymore, as they've gone fully independent not too long ago.

The owner and maintainer of wlroots would fully disagree with you here. Look at this issue which proposes the method you'd need to use to incorporate this workaround.

In the past we've rejected this feature. I believe compositors can do something like this without wlroots changes.

It is very clear that wlroots is telling its compositors, "Hey! If you want to add this feature, do it yourself." This is why you can not rely on upstream to solve this issue for you.

XWayland is not like other Wayland clients. I think it is deserving of a real exemption that should be implemented to improve the user experience.

I'm not great at C++, but if you're willing to give me an overview of how Wayfire upscales clients to match the fractional scaling, I'm sure that we can figure something out. From my skimming of the code, I know that you have to "damage" the client to scale it accordingly.

Morxemplum avatar Jul 10 '25 16:07 Morxemplum

The approaches you mention require wlroots changes as well. This is why you don't see any of the compositors using wlroots which have these workarounds :)

Hmmmmm. It seems like Hyprland used to be wlroots-based but not anymore, as they've gone fully independent not too long ago.

The owner and maintainer of wlroots would fully disagree with you here. Look at this issue which proposes the method you'd need to use to incorporate this workaround.

In the past we've rejected this feature. I believe compositors can do something like this without wlroots changes.

Sure, we can reimplement a few protocols instead of using wlroots ;)

It is very clear that wlroots is telling its compositors, "Hey! If you want to add this feature, do it yourself." This is why you can not rely on upstream to solve this issue for you.

They don't think the solution is the real way to solve the issue. Wlroots tries to stick to upstream solutions.

XWayland is not like other Wayland clients. I think it is deserving of a real exemption that should be implemented to improve the user experience.

We already do a lot of stuff for Xwayland. I am not sure how much more we want to invest ...

I'm not great at C++, but if you're willing to give me an overview of how Wayfire upscales clients to match the fractional scaling, I'm sure that we can figure something out. From my skimming of the code, I know that you have to "damage" the client to scale it accordingly.

We don't really do anything special here. The trick is that clients submit buffers and can set the scale (Wayland native clients do the same as Xwayland). Simple case: output scale 2, physical size 3840x2160, logical size 1920x1089. Xwayland fullscreen application. For Xwayland, we report via the xdg-output protocol the logical size of 1920x1080. Then, Xwayland submits buffers with size 1920x1080 with scale 1. The compositor sees this window is fullscreen (logical coordinates) and draws it over the whole screen. As the physical screen size is 2x bigger, you get blurry upscaling.

Wayland clients recognize the mismatch between physical and logical size. They would submit a buffer of size 3840x2160 and scale 2, which for wayfire means logical size 1920x1080 (so fullscreen, in logical space). The GL/Vulkan texture however has a higher resolution so when we "upscale" it actually is crisp.

If you wanted to make native scaling in Xwayland, you have to apply scaling to everything. That is, we have to hack the xdg-output protocol and report logical dize 3840x2160 (physical resolution) to Xwayland. So that Xwayland lives in a virtual world with no scale at all. We also need to adjust input coordinates and any geometry we set or receive from Xwayland to match the logical coordinates we use everywhere.

ammen99 avatar Jul 10 '25 21:07 ammen99

And since you asked for potential places to adjust in the code:

  1. When we read surface size from wlroots, we'd have to differntiate between xwayland and non-xwayland surfaces here: https://github.com/WayfireWM/wayfire/blob/12ba3460bfecb01cc5693ebbc36e3f7e70eca283/src/view/wlr-surface-node.cpp#L63

As for xwayland surfaces, the size will need to be scaled down.

  1. The xdg-output will have to be reimplemented or you'll need to patch wlroots in order to 'lie' to Xwayland: https://github.com/WayfireWM/wayfire/blob/12ba3460bfecb01cc5693ebbc36e3f7e70eca283/src/core/core.cpp#L148

  2. All places in the Xwayland code where we send coordinates to Xwayland or read xsurface->x/y/w/h, you'd have to adjust to account for the scale. See files like (potentially some others as well, but those would contain most of the usages): https://github.com/WayfireWM/wayfire/blob/master/src/view/xwayland/xwayland-toplevel-view.hpp https://github.com/WayfireWM/wayfire/blob/master/src/view/xwayland/xwayland-toplevel.cpp https://github.com/WayfireWM/wayfire/blob/master/src/view/xwayland/xwayland-unmanaged-view.hpp

ammen99 avatar Jul 10 '25 21:07 ammen99

Sure, we can reimplement a few protocols instead of using wlroots ;)

I can see the sense of humor and sarcasm in this message (which is refreshing compared to elitists just outright being condescending and telling people they're wrong). I know that you are just a small group of people working together to maintain this compositor, and I really respect the work you guys do. It's just unfortunate because of your limited time and resources, you guys seems to be ball and chained to wlroots' conservative rules. I'll try and help, even if it will cost me a bit of my sanity trying to familiarize myself with the codebase, more Wayland terminology, and more C++. 🥹

They don't think the solution is the real way to solve the issue. Wlroots tries to stick to upstream solutions.

Those upstream solutions being wayland-protocols, where my comment about bikeshedding is on full display. Seriously, look at the issue where they're trying to figure out how to identify a default/primary display, a glitch I encounter when running games with the wine-wayland driver. You will find a treasure trove of discussions that would absolutely baffle any game developer or regular user. I don't mean to talk bad, but I can sort of see why people who still defend X11 keep saying that Wayland is not yet ready.

Simple case: output scale 2, physical size 3840x2160, logical size 1920x1089. Xwayland fullscreen application. For Xwayland, we report via the xdg-output protocol the logical size of 1920x1080. Then, Xwayland submits buffers with size 1920x1080 with scale 1. The compositor sees this window is fullscreen (logical coordinates) and draws it over the whole screen. As the physical screen size is 2x bigger, you get blurry upscaling.

Yep, because logical size and coordinates do not exist in X11; it only knows physical size and coordinates. And it's the fact that you report the logical size to the X server that's the root of the problem. And you know the gist of what needs to happen to fix the issue. We'll have to reconvert the logical size back to the physical size and report that to XWayland (and only XWayland).

If you wanted to make native scaling in Xwayland, you have to apply scaling to everything.

Oh yeah I fully expect that this will apply to all XWayland applications. I don't think we should waste time applying scaling on a case-by-case basis. You can't run multiple XWayland servers on a single display anyways.

As for the code you sent me, I'll take a look and see what I can find and do.

Morxemplum avatar Jul 10 '25 23:07 Morxemplum

And it's the fact that you report the logical size to the X server that's the root of the problem.

To be fair, we report both the physical and the logical size. But the ecosystem has settled on Xwayland using the logical size, as that makes communicating with applications marginally simpler (namely we don't have the scaling of coordinates that you'll need to add to get this working correctly).

I'll try and help, even if it will cost me a bit of my sanity trying to familiarize myself with the codebase, more Wayland terminology, and more C++. 🥹

Thanks for your readiness to help and feel free to ask questions if you get stuck. I would also highly recommend joining the matrix channel so that we can chat in real time, if there is a need to.

ammen99 avatar Jul 11 '25 08:07 ammen99

Alright before I start modifying the code, what I always do is I make sure that I can take the master branch, and be able to build the source and run it as is.

I'm using CachyOS (Arch-based) as my distro, and I am having to build wlroots because I run into build errors around vulkan.h using Arch's wlroots0.18 and I have to build wf-config because the AUR's version, similar to wayfire itself, is not up to date with master.

I was able to get a perfect build and install with no errors when running both commands. However, when I go into a spare TTY and try to run wayfire, it immediately runs into errors because it cannot find the shared libraries of the built wlroots and wf-config. These are separate errors and I advanced to the next error message by using Arch's wlroots0.18. I cannot do the same with wf-config because it's not the latest version.

wayfire: error while loading shared libraries: libwlroots-0.18.so.1: cannot open shared object file: No such file or directory
wayfire: error while loading shared libraries: libwf-config.so.1: cannot open shared object file: No such file or directory

If it also helps, I do have an NVidia RTX 3080 running the open NVidia drivers and I am on x86 with an Intel processor.

Morxemplum avatar Jul 11 '25 22:07 Morxemplum

Sounds like you installed these libraries in a different location? You should either install to /usr or, as I would recommend, try wf-install: https://github.com/WayfireWM/wf-install

With it, you can install Wayfire to any prefix like /opt/wayfire and it will also give you a script for starting it with all necessary environmental variables set. I use it to maintain multiple Wayfire installations at different prefixes, very useful for development.

EDIT: and if you don't like wf-install, you could manually set LD_LIBRARY_PATH and any other env vars you need.

ammen99 avatar Jul 11 '25 23:07 ammen99

I see. It installs automatically to /usr/local/share but usually the prebuilts have files at /usr/share. I decided to use your install script and after fixing a couple missing dependencies, I got Wayfire running.

I just thought that the install script was used by people who don't have a package manager and didn't want to modify the source code, so I went with the latter instructions. But this is quite useful as it allows me to isolate my wayfire installations a lot better. And I am guessing that if I want to rebuild Wayfire when I modify the source code, I'll just run the update script.

Useful stuff. Thanks!

Morxemplum avatar Jul 12 '25 00:07 Morxemplum