Closed Bug 1849092 Opened 2 years ago Closed 8 months ago

Window is blurred when fractional scale is used

Categories

(Core :: Widget: Gtk, defect, P3)

defect

Tracking

()

RESOLVED FIXED
145 Branch
Tracking Status
firefox145 --- fixed

People

(Reporter: me, Assigned: emilio)

References

(Blocks 2 open bugs)

Details

Attachments

(9 files, 2 obsolete files)

Steps to reproduce:

Set widget.wayland.fractional-scale.enabled = true, restart Firefox, don't maximize the window.

Running Nightly 2023-08-16 on Plasma 5.27.7. For some reason, maximized windows render correctly. Also, pressing "force device reset" in about:support makes the window not blurry (and slightly smaller, which to me sounds like there might be some rounding error somewhere?) until the next repaint.

Actual results:

The window is blurry.

Expected results:

The window is not blurry.

Flags: needinfo?(stransky)

The Bugbug bot thinks this bug should belong to the 'Core::Widget: Gtk' component, and is moving the bug to that component. Please correct in case you think the bot is wrong.

Component: Untriaged → Widget: Gtk
Product: Firefox → Core
Blocks: wayland
Flags: needinfo?(stransky)
Priority: -- → P3

Is that a recent regression?

Can you try to use mozregression to find a broken commit?
https://fedoraproject.org/wiki/How_to_debug_Firefox_problems#Use_Mozregression_tool
Thanks.

Flags: needinfo?(me)

It only happens with native fractional scaling enabled, which landed in yesterday's Nightly as far as I can tell? The default scaling setup isn't affected.

Flags: needinfo?(me)

My understanding of Firefox's implementation of Wayland is that the toplevel surface is created by Gtk3's window code - Gtk3 draws the window shadows, borders, and background - then Firefox itself renders in a subsurface which is positioned and sized to appear only within the "window area" (excluding shadows) - and for fractional scaling, wl_viewport is only used on the subsurface, not the toplevel?

If so, the cause of this issue is almost certainly due to rounding errors in position and size caused by the differences between the toplevel surface (presumably rendered by Gtk3 at nearest integer buffer scale then upscaled or downscaled to the fractional scale by the compositor) and the subsurface (intended to be rendered directly at fractional scale). The fractional-scale-v1 protocol is only designed to be used for toplevel surfaces: "For toplevel surfaces, the size is rounded halfway away from zero. The rounding algorithm for subsurface position and size is not defined." https://wayland.app/protocols/fractional-scale-v1

I think to fix this fully and completely, Firefox probably would have to handle its own toplevel surfaces rather than going through Gtk (which would also mean that Gtk can't be used for drawing shadows, window borders, etc - and it probably also means that Gtk can't be used for input handling). Some of the later comments on https://bugzilla.mozilla.org/show_bug.cgi?id=1701123 are relevant.

A possible "hack" to avoid the rounding problem might be to have Firefox's subsurface cover the exact same area as Gtk's toplevel surface - i.e. have the subsurface also cover the area where the shadow is rendered. But even if this work, it depends on non-specified behaviour. Also, Firefox's own drawing would have to be padded within the subsurface to only be within the "window area", which brings up new and different rounding error problems (the edges of the window area on Gtk's toplevel surface might not line up with physical pixels).

I should note that on my screen (3840x2160 at 175% in GNOME 44), this issue is also affecting maximized windows. Gtk still draws a border (actually a thin shadow) around the edges of maximized windows, so the rounding error issue happens there too. Fullscreen windows also seem to be affected on my system but I don't have an explanation for why that is happening.

With GNOME 45 I get the reported behavior - blurry rendering unless the window is maximized.

It seems like GNOME 44's implementation of fractional-scale-v1 wasn't complete (see https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2726). I couldn't get any compatible programs to render without blurring.

I'm repeating myself a bit here, but I just want to make it clear - as of GNOME 45, the implementation of the fractional-scale-v1 extension is correct and complete to the best of my knowledge. (GNOME 44 did have a bug in its implementation that affected Chromium, which has since been fixed.) The blurriness of Firefox specifically is caused by a combination of two problems.

  1. The specification of the fractional-scale-v1 extension itself is incomplete, and does not define handling for subsurfaces:

    For toplevel surfaces, the size is rounded halfway away from zero. The rounding algorithm for subsurface position and size is not defined.

    The alternate fractional scaling proposal (now called fractional-scale-v2) did define handling for subsurfaces, but was more involved for compositors to handle due to multiple coordinate systems. It has no implementations, and is currently stalled.

  2. Due to the unfortunate design choice of using Gtk to create "toplevel" windows despite Firefox not actually using Gtk as a GUI toolkit, combined with the fact that Gtk itself does not itself support fractional scaling, and Gtk3 doesn't provide a good/performant way for Firefox to render its own content into the toplevel surface, Firefox currently does all of its own drawing in a positioned and sized subsurface.

As far as I can see, there's one solution and one workaround:

Solution: Stop using Gtk, and have Firefox on Wayland speak wayland protocols itself directly. Firefox is already doing a bunch of wayland stuff directly anyways to bypass Gtk via the subsurface. This would also fix the lag between resizing a window and the Firefox subsurface getting updated - and would give Firefox direct access to input events, fixing existing issues resulting from getting input events after Gtk processes them.

Workaround: You might get lucky, and find out that in existing compositors, having a subsurface positioned at 0,0 and with size the same as the toplevel window (according to the fractional-scale-v1 specified toplevel rounding) happens to align it correctly. This requires that Firefox pad its subsurface with transparency over the areas where Gtk is drawing the window shadows and borders. There is of course a rounding problem here, since the Gtk toplevel surface is not fractionally scaled, so the borders/shadows that it draws will not lineup with physical pixels.

(In reply to Calvin Walton from comment #6)

Solution: Stop using Gtk, and have Firefox on Wayland speak wayland protocols itself directly. Firefox is already doing a bunch of wayland stuff directly anyways to bypass Gtk via the subsurface. This would also fix the lag between resizing a window and the Firefox subsurface getting updated - and would give Firefox direct access to input events, fixing existing issues resulting from getting input events after Gtk processes them.

Amount of such work would be enormous. But well, we can look that way of course.

It seems that non-maximized window is no longer blurry under KDE 6.3

Blurry rendering with windowed firefox (firefox 136.0, kwin 6.3.2 150% scaling, widget.wayland.fractional-scale.enabled=true)

Crisp rendering with fullscreen firefox (firefox 136.0, kwin 6.3.2 150% scaling, widget.wayland.fractional-scale.enabled=true)

(In reply to sheng from comment #9)

It seems that non-maximized window is no longer blurry under KDE 6.3

Unfortunately this is not what I experience, see the provided attachments. This is issue is still pretty much present with Firefox 136.0 and kwin 6.3.2.

Can confirm this continues to be a problem, tested in both Gnome 48 and KDE 6.3.

Using desktop 150% scaling in both. widget.wayland.fractional-scale.enabled = true in about:config

If I manually resize the window, there are very, very specific width+height sizes where it looks incredibly sharp. Whereas the rest look blurry. Some combinations of width+height look way worse than others, but as mentioned, I guess there's a combination when it matches a rounding number or something and looks super crisp. For some, this seems to be maximized (not for me). But, fullscreen Firefox does it for me.

(In reply to alejandro9r from comment #13)

Can confirm this continues to be a problem, tested in both Gnome 48 and KDE 6.3.

Using desktop 150% scaling in both. widget.wayland.fractional-scale.enabled = true in about:config

If I manually resize the window, there are very, very specific width+height sizes where it looks incredibly sharp. Whereas the rest look blurry. Some combinations of width+height look way worse than others, but as mentioned, I guess there's a combination when it matches a rounding number or something and looks super crisp. For some, this seems to be maximized (not for me). But, fullscreen Firefox does it for me.

Just as a silly workaround in Gnome to make it look crisp maximized or snapped 50% side-by-side (I use it a lot when doing web dev work):

  • Install Dash To Panel extension
  • If you wish, you can configure the extension it to literally look the same as the top bar panel just with the settings in the "Position" section
  • Then, maximize Firefox, while tweaking the Panel thickness option with pixel precision until it looks crisp. Mine is 26px @ 4K resolution 3840x2160 @ 150% scaling

Can you please test again with latest nightly?
https://fedoraproject.org/wiki/How_to_debug_Firefox_problems#Testing_Mozilla_Nightly_binaries

Also is that only with widget.wayland.fractional-scale.enabled=true set?
Thanks.

Flags: needinfo?(me)
Flags: needinfo?(alejandro9r)
Flags: needinfo?(gilles)

(In reply to Martin Stránský [:stransky] (ni? me) from comment #15)

Can you please test again with latest nightly?
https://fedoraproject.org/wiki/How_to_debug_Firefox_problems#Testing_Mozilla_Nightly_binaries

Also is that only with widget.wayland.fractional-scale.enabled=true set?
Thanks.

Testing it with Firefox Nightly 144.0a1 (2025-09-11) under Gnome 48, Wayland, Scaling 150%, 3840x2160px

  • Same results as Firefox 142.0.1 for both fractional-scale.enabled=true and false, nothing really changed

  • In terms of sharpness and pixel precision, it follows this order (confirmed on Nightly):

  1. best ---- widget.wayland.fractional-scale.enabled=true WITH the right width+height of the window
  2. 2nd best, blurry 1px borders ---- widget.wayland.fractional-scale.enabled=false
  3. worst, blurriest ---- widget.wayland.fracional-scale.enabled=true WITHOUT the right width+height of the window
  • In terms of respecting font and typefaces aspect ratio (confirmed on Nightly):
  1. best ---- widget.wayland.fractional-scale.enabled=false
  2. worst, enlarged fonts ---- widget.wayland.fractional-scale.enabled=true

Some typefaces seem to be slightly vertically enlarged, sometimes in a noticeable manner particularly with small text when fractional.scale is enabled.

Chrome based browsers with Wayland: pretty much flawless rendering of text, typefaces, borders of any pixel without blur, etc. No matter the height and width of the window, it looks always the same.

Flags: needinfo?(alejandro9r)

I concur.

With Firefox 144.0a1 (2025-09-11) on Plasma 6.4.4, 150% scaling 4k resolution, widget.wayland.fractional-scale.enabled=true, the issue is still reproducible and the latest nightly doesn't bring any improvement: maximized main window is crisp, unmaximized main window is blurry (unless at very specific window sizes), menus/popups are always blurry.

With widget.wayland.fractional-scale.enabled=false, by design rendering is also always a bit blurry due to the compositor (kwin) downscaling the buffer rendered at 2x by firefox.

Please run on terminal with MOZ_LOG="Widget:5 WidgetScreen:5" and try to setup "widget.wayland.fractional-scale.enabled=true WITH the right width+height of the window" scenario and attach the log here.
Thanks.

I think we're hitting a rounding error bug between internal Firefox rendering scale and screen scale. Firefox renders the content with fixed scale but then we extend the painting to widget window boundaries (which are rounded to underlying Gtk3 integer scale) and that makes the content blurry.

We may adjust the internal rendering scale to actual window size after resize to make sure fractional/integer rounding falls into the same sizes.

Right now I'm trying to figure out how to get the correct sizes. btw. does Gtk4 support the fractional scaling? If so Bug 1701123 may be related then.

Can you try to set gfx.webrender.software=true and restart to see if it fixes the blurred rendering? It may be also caused by wrong EGL setup.
Thanks.

I think we're hitting a rounding error bug between internal Firefox rendering scale and screen scale. Firefox renders the content with fixed scale but then we extend the painting to widget window boundaries (which are rounded to underlying Gtk3 integer scale) and that makes the content blurry.

This is caused by Firefox's use of subsurfaces, not (directly) its use of Gtk3. The wayland fractional scaling extension does not provide any way to have predictable positioning and sizing of subsurfaces while fractional scaling is in use ("The rounding algorithm for subsurface position and size is not defined."), which means that the subsurface that Firefox renders to might have position and size rounded (by the wayland compositor) differently from what is expected.

Gtk4 does support fractional scaling, but the issue with subsurfaces would still occur when using Gtk4 because subsurface position and size are always communicated using wayland "logical" integer pixel coordinates. That said, Gtk4 provides capabilities to composite textures from external renderers into the toplevel surface (via DMA-BUF, for example), which would solve the problem by removing the need for a subsurface. Alternately, Firefox could manage toplevel surfaces itself (Bug 1861980).

Can confirm seeing this bug on Nightly, KDE git master 150% scaling, Wayland, Tumbleweed, with gfx.webrender.software both true and false. Seems to be happening mostly with widget.wayland.fractional-scale.enabled=true, not very much with false if at all.

Changing window size pixel-by-pixel makes things blurry or sharp, quite drastically. And the x- and y-axes seem independent, so getting maximum sharpness requires setting both the perfect horizontal and vertical size (mod 3 or something). Thus fullscreen with perfect KDE/Gnome panel height might only remedy the issue vertically, if the physical screen resolution happens to not be a perfect horizontal size (this is the case for me).

I see Calvin saying it seems like a subsurface issue, but would us providing a MOZ_LOG="Widget:5 WidgetScreen:5" log still help?

I just noticed, I think this bug applies to hover tooltips as well. Some have crisp text and some have blurry text, which I'm guessing is due to differences in the rectangle's width.

Simple example is hovering over our usernames here, e.g for me the tooltips for Calvin and Gilles are blurry, while the tooltips for Martin and me are sharp.

(In reply to Tem PQD [:tempqd] from comment #23)

Can confirm seeing this bug on Nightly, KDE git master 150% scaling, Wayland, Tumbleweed, with gfx.webrender.software both true and false. Seems to be happening mostly with widget.wayland.fractional-scale.enabled=true, not very much with false if at all.

Changing window size pixel-by-pixel makes things blurry or sharp, quite drastically. And the x- and y-axes seem independent, so getting maximum sharpness requires setting both the perfect horizontal and vertical size (mod 3 or something). Thus fullscreen with perfect KDE/Gnome panel height might only remedy the issue vertically, if the physical screen resolution happens to not be a perfect horizontal size (this is the case for me).

I see Calvin saying it seems like a subsurface issue, but would us providing a MOZ_LOG="Widget:5 WidgetScreen:5" log still help?

Yes please, provide me the MOZ_LOG="Widget:5 WidgetScreen:5" log. When you see sharp rendering, just quit the browser.
Thanks.

Also can you compare how Gnome dialog looks like compared to Firefox? For instance Display settings panel? Can you post screenshot where blurry/sharp firefox is rendered side by gnome setting dialog?

I want to make sure I understand the differences here as I may not have good eyes/monitor to see that on my end.
Thanks.

I've attached 3 attempts at logging the issue (it's tricky!) and two screenshots side-by-side with KDE settings. Is that okay, or would you like specifically Gnome settings?

Also, I keep getting blocked from accessing Bugzilla with "We’re sorry, but you have sent too many requests to us recently. You will be unblocked in 60 minutes." -- is there a way I could prevent that? :)

Keep in mind to properly view the screenshots, one has to download the image and (hopefully!) the image viewer app should be set at 100% (1:1) and pray it will respect proper scaling as well if using a high DPI display. Otherwise setting the scaling of the desktop temporarily to 100% does the trick

Also, I keep getting blocked from accessing Bugzilla with "We’re sorry, but you have sent too many requests to us recently. You will be unblocked in 60 minutes." -- is there a way I could prevent that? :)

Maybe? These limits are because Bugzilla is constantly under DOS and scraping attacks. Assuming you are not, in fact, personally sending a large number of requests then you likely are sharing an IP address with a lot of other people—maybe you use a VPN, or your connections come from a NAT address for a large enterprise or university network. If you go to the #bmo channel on https://chat.mozilla.org/ (or #bmo:mozilla.org on another Matrix server) they may be able to increase the limits for that IP if it's stable and not a source of abuse from other people connecting from the same address.

We also just switched a day or two ago from one WAF service to another, and that's causing a bunch of problems. If that's what you're seeing it will get worked out as our network folks tune the new WAF rules over the coming days.

Thanks for the screenshots! Looks like the blurred one has slightly bigger content (I mean bigger painted fonts/etc) - It confirms my theory that blurred rendering is caused by missed 1:1 rendering. I think we paint the buffer (say 1000x500 pixels) and then render it a bit bigger/smaller due to widget borders or so (say 1001x500 or 999x500).

AFAIK the widget boundaries are updated by integer scale step. For instance 225% scale means 3.0 integer scale and Gtk window sized 600x400 is placed at screen as 1800x1200 pixels. So we need to make sure we put correct buffer size to webrender and keep that size when we paint it. I think Bug 1988717 is another incarnation of this one - we paint the buffer incorrectly.

(It's also possible that the subsurface rounding is the issue here but let's fix the rendering size first).

Thanks :dveditz, looks like it's settled out today but if it starts happening again I'll reach out.

Ah, if it can be fixed first without diving into the subsurface stuff that'd be nice!

I'm gonna go through a bit of the log. I have to make some assumptions since I'm not that familiar with Firefox's code.

[Parent 85769: Main Thread]: D/WidgetScreen New monitor 0 size [0,0 -> 2560 x 1600] depth 24 scale 1.499707 CssScale 1.499707  DPI 189.023254 refresh 165 HDR 0]

You're using a 2560x1600 monitor at 150% scale (laptop screen, i assume?) but the compositor has rounded the scaled size to an integer number of logical pixels. Uh-oh. We're actually in GNOME bug territory here. See mutter#1135 and mutter#3339 for some details.

If the display resolution divided by the selected fractional scale does not result in an integer, Mutter will actually slightly adjust the scale to reduce the error. But the adjusted scaling factor can't be exactly represented in the fractional-scaling-v1 protocol, so there is a mismatch between the buffer sizes apps try to use and the size that Mutter thinks they should be.

Note that the "1.499707" value reported here is not the scaling factor that Mutter is using internally. If I had to guess, Firefox calculated that from only the width of the screen - 2560/1707=1.499707… - but the scaling factor internally used by Mutter tries to minimize the error on both width and height, and this value gives 1600/1.499707=1066.875 logical pixels in height, which is still a pretty big error.

Note that in GNOME 49 a workaround is added - the Settings panel no longer allows you to select scaling factors which trigger this bug. (A few new scaling factors were added to give you options to replace the ones that are no longer available.)

[Parent 85769: Main Thread]: D/Widget [7f2b47a11300]: configure event 0,0 -> 1373 x 1025 direct mGdkWindow scale 2 (scaled size 2746 x 2050)

Ok, Gtk has created a 1373x1025 surface at 2× integer scale.

[Parent 85769: Main Thread]: D/Widget [7f2b47a11300]: nsWindow::OnContainerSizeAllocate 45,45 -> 1283 x 935
``
The widget filling the window is 1283x935, offset from the top and left by 45px. This 45px applies on all sides, and this looks like the space used by Gtk to draw the window border and shadow. 1283+45+45=1373, and 935+45+45=1025, so this all adds up so far.

[Parent 85769: Main Thread]: D/Widget [7f2b47a11300]: bounds: (x=0, y=0, w=2059, h=1537) -> (x=0, y=0, w=2059, h=1537) ((x=0, y=0, w=2059, h=1537) unconstrained)
[Parent 85769: Main Thread]: D/Widget [7f2b47a11300]: margin: (t=0, r=0, b=0, l=0) -> (t=68, r=67, b=67, l=68)
``
I don't understand these numbers. It looks like they're supposed to be a guess at the physical pixel size of the window, but the rounding seems suspect? 1373*1.5=2059.5 so I'd expect that to round to 2060px. Was this calculated using the 1.499707 scale factor calculated incorrectly from the monitor size rather than the 1.5 scale factor reported by the fractional scaling extension?

[Parent 85769: Renderer]: V/Widget [7f2b47a11300]: nsWindow::FractionalScaleFactor(): fractional scale 1.500000
[Parent 85769: Renderer]: V/Widget [7f2b47a11300]: nsWindow::SetEGLNativeWindowSize() 1924 x 1402 scale 1.500000 (unscaled 1282.666626 x 934.666687)

If subsurfaces using the fractional scaling extension have size rounded in the same way as specified for toplevel surfaces, then given the subsurface is 1283 x 935 and the scale reported by the fractional scaling extension is 1.5x, the expected size to allocate for the buffer should be 1925x1403, not 1924x1402.

Ah, were these logs from a system running KDE? If so, my notes on the GNOME bug don't apply. To the best of my understanding, KWin does do the rounding of fractionally scaled toplevel surface size correctly (not sure about subsurfaces) even when the scale factor does not result in the monitor having an integer scaled height and width.

Regardless, it still appears that Firefox is incorrectly calculating the size to use for the fractionally scaled subsurface using a scale factor calculated from the screen's reported pixel size rather than the scale factor reported by the fractional scaling extension. It's possible that this is the only source of rounding error.

Previous comment points to mutter issues, one of which mentions enlarged and weird text rendering. So far in this discussion we are focused on blurriness, but want to voice on enlarged fonts being also present with fractional-scale enabled on Firefox, as I stated in my previous comments.

I don't reach the level of knowledge necessary to make sound arguments about the source of the problem. Maybe it's all related and part of the same problem I guess.

I've experienced this on Gnome though. I haven't tested KDE to see if their compositor does not present the enlarged text under fractional-scaling=true issue. If so, then chances are this is almost a Firefox issue right?

Thanks for the thorough analysis! My screen is indeed a laptop and my logs were indeed on KDE git master (and we're reachable in the #kwin:kde.org matrix room as well for scaling/surface/subsurface questions).

Previous comment points to mutter issues, one of which mentions enlarged and weird text rendering. So far in this discussion we are focused on blurriness

The GNOME/Mutter bugs that I mentioned cause the same symptoms as this Firefox issue (slightly blurred window contents / text) and affect multiple applications. But since the Firefox issue can be reproduced on non-GNOME desktops, it's a separate problem.

Testers on GNOME need to be careful, since there's two possible sources of blurriness that could get confused with each other. (alternately, the two issues might cancel each other out and result in Firefox not being blurry!)

but want to voice on enlarged fonts being also present with fractional-scale enabled on Firefox

The problem being discussed here is caused by the entire contents of the window being scaled just one pixel larger or smaller. The blur caused by this scaling is easier to see than the scaling itself. If you're seeing more significant font size differences (especially if only the font size is wrong, and not anything else), it must have a different cause.

See https://bugzilla.mozilla.org/show_bug.cgi?id=1988717#c7 - there's a bug with misaligned rendering. It may be relevant to this one too.

I'll check the raw painting from wl_buffers how the rendering looks like and if the blur is produced by Firefox itself or by Wayland rendering. It's very well reproducible with 125% scale on my recent testing box & Gnome & Fedora 42.

Flags: needinfo?(stransky)
Flags: needinfo?(me)
Flags: needinfo?(gilles)

Martin - I'd like to double-check to make sure that you're not hitting the Mutter bug when testing at 125% scale. What resolution of screen are you using?

Actually, based on my analysis of the logs earlier, it's likely that the same conditions which trigger the Mutter bug (scaled screen size does not have integer dimensions at the selected scale factor) is also what triggers the Firefox bug. That might make this issue impossible to isolate on GNOME.

For debugging purposes, you can check what scaling factor GNOME is actually using by reading the file ~/.config/monitors.xml and looking for the <scale> element. As far as I know, this value is not exposed in any public API.

I'd highly recommend attempting to reproduce this issue in KDE, not GNOME, to avoid the problems caused by the Mutter bug.

I've attached requested logs of Firefox 143.0 on Plasma 6.4.5, 4k resolution, 150% scaling, with several resize attempts at making the rendering crisp, the last one being successful.

I would also add that while this issue can be reproduced on Gnome with Mutter as well, the initial reporter Ilya K, alejandro9r, Tem PQD, as well as myself, are all triggering this issue with the KWin compositor.

Taking a look through the log provided by Gilles:

[Parent 382889: Main Thread]: D/WidgetScreen New monitor 0 size [0,0 -> 5120 x 2880] depth 24 scale 2.000000 CssScale 2.000000  DPI 203.199997 refresh 144 HDR 0]

My theory that the incorrect scale factor came from the monitor has been disproved :) But I find it a bit odd that the monitor scale is detected as 2.0 while the fractional scaling reports 1.5. Also, you said a 4K monitor, but this is a 5K resolution? Can you confirm the exact pixel resolution of your monitor?


On the initial window creation:

[Parent 382889: Main Thread]: D/Widget [7f17d0f87700]: configure event 0,0 -> 1191 x 933 direct mGdkWindow scale 2 (scaled size 2382 x 1866)
[Parent 382889: Main Thread]: D/Widget moz_container_wayland_size_allocate [7f17d0f87700] 45,45 -> 1101 x 843
[Parent 382889: Main Thread]: D/Widget [7f17d0f87700]: nsWindow::OnContainerSizeAllocate 45,45 -> 1101 x 843

Ok, 1191x933 gtk surface at 2x integer scale. Accounting for the 45px margins for the border/shadow, that means the size of the Firefox subsurface is 1101x843.

[Parent 382889: Main Thread]: V/Widget [7f17d0f87700]: nsWindow::FractionalScaleFactor(): fractional scale 1.500000
[Parent 382889: Main Thread]: D/Widget [7f17d0f87700]: bounds: (x=0, y=0, w=1786, h=1399) -> (x=0, y=0, w=1786, h=1399) ((x=0, y=0, w=1786, h=1399) unconstrained)
[Parent 382889: Main Thread]: D/Widget [7f17d0f87700]: margin: (t=68, r=67, b=67, l=68) -> (t=68, r=67, b=67, l=68)

I still don't understand how these bounds and margin numbers are being calculated (in particular, why are they being calculated at the fractional scaling size, when subsurfaces are laid out using scaled pixels for position and size?). 1191×1.5=1786.5, so the correct value for w would be 1787. 933×1.5=1399.5 so the correct value for h would be 1400. The margins should be 45*1.5=67.5 each, so rounding one side up and one side down… kind of works out?

[Parent 382889: Renderer]: V/Widget [7f17d0f87700]: nsWindow::SetEGLNativeWindowSize() 1651 x 1264 scale 1.500000 (unscaled 1100.666626 x 842.666687)

The size of the rendering is 1651x1264, but it should be 1652x1265 (1px bigger in both dimensions) if calculated from the 1101x843 subsurface size with the same rounding rules as specified for toplevel surfaces.


Jumping to the last size update:

[Parent 382889: Main Thread]: D/Widget moz_container_wayland_size_allocate [7f17d0f87700] 45,45 -> 1022 x 1046
[Parent 382889: Main Thread]: D/Widget [7f17d0f87700]: nsWindow::OnContainerSizeAllocate 45,45 -> 1022 x 1046
[Parent 382889: Main Thread]: D/Widget [7f17d0f87700]: configure event 0,0 -> 1112 x 1136 direct mGdkWindow scale 2 (scaled size 2224 x 2272)
[Parent 382889: Main Thread]: D/Widget [7f17d0f87700]: bounds: (x=0, y=0, w=1668, h=1704) -> (x=0, y=0, w=1668, h=1704) ((x=0, y=0, w=1668, h=1704) unconstrained)
[Parent 382889: Main Thread]: D/Widget [7f17d0f87700]: margin: (t=68, r=68, b=68, l=68) -> (t=68, r=68, b=68, l=68)

Window size 1112x1136, wayland subsurface size is 1022x1046.

In this case, the bounds are calculated correctly (1112×1.5=1668, 1136×1.5=1704, no rounding required). But the values for margin don't add up, since 68+68=136, but (45+45)*1.5=135. I can't find any message saying directly what the size of texture Firefox is rendering to. Since the subsurface is 1022x1046, Firefox should be providing a 1533x1569 texture.

Interesting - If I scroll up a bit from the bottom, I find this:

[Parent 382889: Main Thread]: D/Widget [7f17d0f87700]: configure event 0,0 -> 1112 x 1136 direct mGdkWindow scale 2 (scaled size 2224 x 2272)
[Parent 382889: Main Thread]: D/Widget [7f17d0f87700]: RecomputeBounds(0)
[Parent 382889: Main Thread]: V/Widget [7f17d0f87700]: nsWindow::FractionalScaleFactor(): fractional scale 1.500000
[Parent 382889: Main Thread]: V/Widget [7f17d0f87700]: nsWindow::FractionalScaleFactor(): fractional scale 1.500000
[Parent 382889: Main Thread]: D/Widget [7f17d0f87700]: bounds: (x=0, y=0, w=1668, h=1702) -> (x=0, y=0, w=1668, h=1704) ((x=0, y=0, w=1668, h=1704) unconstrained)
[Parent 382889: Main Thread]: D/Widget [7f17d0f87700]: margin: (t=68, r=67, b=67, l=68) -> (t=68, r=67, b=67, l=68)
[Parent 382889: Main Thread]: D/Widget [7f17d0f87700]: nsWindow::DispatchResized() size [1668, 1704]
[Parent 382889: Main Thread]: D/Widget [7f17d0f87700]: GtkCompositorWidget::NotifyClientSizeChanged() to 1533 x 1569

In this case, same 1112x1136 toplevel surface size, so the subsurface should still be 1022x1046. The bounds are calculated correctly (1668x1704), and the margin still has the oddly rounded, but workable, 67+68=135 values. The GtkCompositorWidget::NotifyClientSizeChanged is reporting the desired 1533x1569 dimensions for the texture.

If I had to guess, this is probably the point at which the display was not blurred. (The later events might have been something strange happening when the window was being closed)

To the best of my understanding, the flow that I would expect to see is something along the lines of:

  • Observe the GtkWindow toplevel changed to a new size (e.g. 1191 x 933, scale 2, scaled size 2382 x 1866)
  • Calculate and update the size and position of the wayland subsurface in the surface coordinate space (e.g. size: 1101 x 843, position: 45,45)
  • Calculate and update the render size/scaled client area from the size of the wayland subsurface. Use the fractional scaling factor if fractional scaling is enabled and available, otherwise use the integer scaling factor. Use rounding as specified for wayland toplevel surfaces and hope that compositors use the same rounding for subsurfaces. (e.g. 1011 x 843 at 1.5x gives 1516.5 x 1264.5 which rounds to 1517 x 1265)

I'm using 3440x1440 @ 125%. Yes, looks like I'm hitting the rounding bug here. I'm using modified Firefox which used correct client area sizes from allocation events directly and yes, I see the blur only when final subsurface size is not rounded to pixels.
I'll try KDE and I may also try to implement direct rendering to surfaces (not subsurfaces) to confirm that. (The direct surface rendering may not be production ready - just for testing only).

Just tested KDE with correct client area size patch and I can't reproduce the blur bug with the same setup (3440x1440 @ 125%).

Note that the "1.499707" value reported here is not the scaling factor that Mutter is using internally. If I had to guess, Firefox calculated that from only the width of the screen - 2560/1707=1.499707… - but the scaling factor internally used by Mutter tries to minimize the error on both width and height, and this value gives 1600/1.499707=1066.875 logical pixels in height, which is still a pretty big error.

Firefox uses scale factor reported by fractional-scale-v1 protocol. Scale factor derived from monitor sizes is used for initial rendering when per-surface scale is not available.

I think I'll fix that for KDE to render the output always correctly and then ask gnome folks to fix that on their side. I can confirm that Chromium renders the output correctly under Gnome too. I also tested output buffers and Firefox itself render the buffer sharp so there's definitely an issue with moving the picture to screen.

Flags: needinfo?(stransky)
Summary: Window is blurred when not maximized with widget.wayland.fractional-scale.enabled → Window is blurred when fractional scale is used
Flags: needinfo?(stransky)

Nice, thanks! Is that patch okay for me to test now on KDE or should I wait for more changes?

I'm using 3440x1440 @ 125%
I think I'll fix that for KDE to render the output always correctly and then ask gnome folks to fix that on their side.

3440x1440 @ 125% does not trigger the GNOME bug (gives an integer scaled pixel size - 2752x1152). The issue has been "fixed" In GNOME 49 by filtering the list of available fractional scaling factors to remove the problematic ones. On a 3440x1440 screen, the available fractional scale factors will be 125%, 133%, and 166%. (The 150% and 175% options have been removed, 133% and 166% have been added.)

I can confirm that Chromium renders the output correctly under Gnome too.

Chromium draws to a toplevel surface, and you're not hitting the GNOME issue, so this is expected. Hopefully the proposed Firefox patch also works on GNOME… if it does not, then the two compositors must be using different subsurface rounding behaviour.

If that's the case, it might be possible to avoid the problem by making the subsurface the same size as the toplevel surface, positioned at (0, 0), with transparent padding to allow Gtk's window borders / shadows to show through. This option was also mentioned in a comment on the wp-fractional-scale-v2 MR.

(In reply to Calvin Walton from comment #55)

I'm using 3440x1440 @ 125%
I think I'll fix that for KDE to render the output always correctly and then ask gnome folks to fix that on their side.

3440x1440 @ 125% does not trigger the GNOME bug (gives an integer scaled pixel size - 2752x1152). The issue has been "fixed" In GNOME 49 by filtering the list of available fractional scaling factors to remove the problematic ones. On a 3440x1440 screen, the available fractional scale factors will be 125%, 133%, and 166%. (The 150% and 175% options have been removed, 133% and 166% have been added.)

Okay, Thanks.

I can confirm that Chromium renders the output correctly under Gnome too.

Chromium draws to a toplevel surface, and you're not hitting the GNOME issue, so this is expected. Hopefully the proposed Firefox patch also works on GNOME… if it does not, then the two compositors must be using different subsurface rounding behaviour.

I tested the patch with KDE, Gnome and Gnome+toplevel rendering and it works for KDE & Gnome+toplevel rendering. So looks like mutter uses different rounding for subsurfaces.

If that's the case, it might be possible to avoid the problem by making the subsurface the same size as the toplevel surface, positioned at (0, 0), with transparent padding to allow Gtk's window borders / shadows to show through. This option was also mentioned in a comment on the wp-fractional-scale-v2 MR.

That's interesting idea, thanks. I wonder if we can set the offset somehow - by wp_viewporter for instance.

Instead of eagerly rounding and deriving the client rect from the client
margin and the frame bounds, round correctly the client rect first, then
derive the margin from it.

This keeps the coordinates correct while getting the correct client
size.

Remove GetClientOffsetInGDKCoords since afaict we'll never have a useful
position until the gtkwindow size allocate, at which point we already
pass the right position from MozContainer.

Assignee: nobody → emilio
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true

Didn't mean to take the bug directly, just wanted to send a potentially more consistent approach.

Assignee: emilio → nobody
Status: ASSIGNED → NEW
Assignee: nobody → emilio
Status: NEW → ASSIGNED
Attachment #9514915 - Attachment is obsolete: true
Flags: needinfo?(stransky)

LGTM. I'd like to rewrite the code a bit to avoid all the rounding/scaling but let's land this one first to get the fix in place.

Hm, actually the patch doesn't work for me, I see the KDE blur with it too.

Flags: needinfo?(stransky)

Ah, I think I see what's going on. The bounds are correct but LayoutDeviceIntRect::Round is not quite doing what we want. We want RoundOut I think, or rounding the size separately.

GetBounds(774, 483) -> (x=78.75, y=78.75, w=1354.5, h=845.25), (x=79, y=79, w=1354, h=845), (1355 x 845)

I think all the rounding stuff here is very fragile and can be easily wrong. Also it's not tested on try at all (AFAIK it uses scale 1 everywhere). I'm not against a quick fix but I'd rather make it more clear and error prone in long term.

Hopefully you mean more clear and less error prone? ;)

The last version of the patch gets behavior equivalent to the one your patch had (rounding the size independently, effectively), I added a warning comment to that code pointing back to this bug...

Works now for me, thanks.

Ah, I think I see what's going on. The bounds are correct but LayoutDeviceIntRect::Round is not quite doing what we want. We want RoundOut I think, or rounding the size separately.

GetBounds(774, 483) -> (x=78.75, y=78.75, w=1354.5, h=845.25), (x=79, y=79, w=1354, h=845), (1355 x 845)

I'm still not understanding what's going on here. Are you converting from logical pixels to physical pixels, then calculating the surface position and offset, then converting back to logical pixels when creating/positioning the subsurface?

In the wayland protocol, the size and position of the subsurface are all in integer values of logical pixels (the same units as what Gtk uses internally for layout). The position and size of the subsurface should be calculated using logical pixels, which means that no conversion or rounding is required. The calculation of subsurface position and size should be completely independent of the scale factor (i.e. the values should always match what you get when running at 1× scale).

The only places where the scale factor and rounding behaviour matter are when determining how large the buffer should be for a given subsurface, and when drawing in the buffer.

That's interesting idea, thanks. I wonder if we can set the offset somehow - by wp_viewporter for instance.

The viewporter extension does not support offsetting the surface buffer (you cannot "crop" with negative x and y values), so the padding has have to be included in the buffer/texture generated by Firefox.

(In reply to Calvin Walton from comment #66)

Ah, I think I see what's going on. The bounds are correct but LayoutDeviceIntRect::Round is not quite doing what we want. We want RoundOut I think, or rounding the size separately.

GetBounds(774, 483) -> (x=78.75, y=78.75, w=1354.5, h=845.25), (x=79, y=79, w=1354, h=845), (1355 x 845)

I'm still not understanding what's going on here. Are you converting from logical pixels to physical pixels, then calculating the surface position and offset, then converting back to logical pixels when creating/positioning the subsurface?

Yes, Firefox's widget abstractions generally require an integer number of physical pixels. We could change that but it's a rather invasive change.

In that log, 774x483 is the GTK size (logical, in gdk pixels). My scale was 175% iirc. The client area in physical pixels is positioned at (78.75,78.75) (the window decoration offset of the breeze theme), and the size converted to physical pixels is 1354.5x845.25. But we need an integer number of them, and if you round the size separately you get 1355x845, vs. if you round the rect to integer coordinates using xmost/ymost, where you end up with 1354 in that case.

Yes, Firefox's widget abstractions generally require an integer number of physical pixels. We could change that but it's a rather invasive change.

I'm curious why you need to know the integer number of physical pixels for the toplevel surface size and subsurface offset. Is this for something like… input handling or popup menu positioning maybe, where you need to convert back and forth between a position in toplevel surface logical pixel coordinates and subsurface physical pixel coordinates?

In that log, 774x483 is the GTK size (logical, in gdk pixels). My scale was 175% iirc. The client area in physical pixels is positioned at (78.75,78.75) (the window decoration offset of the breeze theme), and the size converted to physical pixels is 1354.5x845.25. But we need an integer number of them, and if you round the size separately you get 1355x845, vs. if you round the rect to integer coordinates using xmost/ymost, where you end up with 1354 in that case.

Gtk layout and wayland communication of surface positions/sizes/offsets all happens in logical pixels. The subsurface is positioned at (45,45). When the compositor draws the subsurface while the window is fully on a single output at 1.75× scale, the subsurface might be placed at rounded physical pixel offsets (78, 78), (78, 79), (79, 78), or (78, 78) at the compositor's discretion. Assuming 774x483 is the client area (subsurface size), then the subsurface might be scaled to either 1354x845 or 1355x845 at the compositor's discretion.

If Firefox really does want to accurately know and control the pixel offsets of everything, then switching to having the subsurface be the same dimensions as the toplevel surface (at 0, 0 offset) make sense. Then you probably can use the rounding specified by the fractional-scaling-v1 extension to calculate the correct buffer size to match the compositor's expectations, and that will give you an integer number of physical pixels corresponding to the exact same screen area as the toplevel window surface, and that can be used as the basis for all of the other calculations.

Status: ASSIGNED → RESOLVED
Closed: 8 months ago
Resolution: --- → FIXED
Target Milestone: --- → 145 Branch

(In reply to Calvin Walton from comment #69)

Yes, Firefox's widget abstractions generally require an integer number of physical pixels. We could change that but it's a rather invasive change.

I'm curious why you need to know the integer number of physical pixels for the toplevel surface size and subsurface offset. Is this for something like… input handling or popup menu positioning maybe, where you need to convert back and forth between a position in toplevel surface logical pixel coordinates and subsurface physical pixel coordinates?

You can look at all the methods in nsIWidget that return LayoutDevice pixels and see what they're used for. There's event targetting etc, but also sizing of graphics buffers and so on.

In general it's used as the "global" coordinate space that's independent of windows. Of course Wayland doesn't expose window positions and so, so it's not that useful there. But all that code also needs to work on all platforms.

(In reply to Calvin Walton from comment #69)

If Firefox really does want to accurately know and control the pixel offsets of everything, then switching to having the subsurface be the same dimensions as the toplevel surface (at 0, 0 offset) make sense. Then you probably can use the rounding specified by the fractional-scaling-v1 extension to calculate the correct buffer size to match the compositor's expectations, and that will give you an integer number of physical pixels corresponding to the exact same screen area as the toplevel window surface, and that can be used as the basis for all of the other calculations.

I was thinking about that change. We'd need to update our rendering somehow to cover that as viewporter can't be used. Maybe wl_egl_window_resize() for HW rendering and actual rendering shift for SW surfaces. BUT that doesn't solve HDR rendering (Bug 1642854) where subsurface tree is used and subsurfaces are used as overlays for direct video and so on.

So we should go with the recent path which allows subsurface positioning and ask compositor guys to fix that (KDE seems to use "Firefox subsurface friendly" rounding already).

(I don't know how HDR is implemented on Chrome side for Wayland, if they use subsurfaces / direct rendering as Firefox or they just blend HDR/SDR content or is not supported at all.)

Attachment #9514681 - Attachment is obsolete: true
Duplicate of this bug: 1919890
Flags: needinfo?(stransky)
QA Whiteboard: [qa-triage-done-c146/b145]
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: