-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Renderer is blurry when window zoom level is changed #2662
Comments
Just a small comment - this affects HiDPI settting in Windows 10 as well. OTOH, DOM is also affected but looks different (no terminal renderer is able to provide the crispiness of the main editor though :-( ). EDIT: Forgot I got redirected to xterm.js, I am referring to usage in VSCode as terminal. EDIT 2: False alarm with DOM, it appears that the scaling is just unfortunate for bold fonts. |
Some reports in VS Code that it happens without a changes zoom level. |
I have cloned the current master and run the demo like described in the wiki. This is how it looks like with Firefox on Windows 10 with a DPI scaling factor of 125%: It might seem okay on first sight, but the You can see that the leftmost characters are rendered way more clearly than the ones farther on the right side. This happens only with the canvas renderer. Switching to the dom renderer leads to much crisper and consistent visualization: With the dom renderer, the three |
@carlfriedrich good observation, I guess this is just another case of devicePixelRatio not being 1 or 2, so the same issue as window.zoomLevel not being 0. |
I took some time to dig into this a bit deeper. @Tyriar Please excuse if I'm repeating what you possibly already know. However, I think this might help other people understand why the issue still exists. General observations
The last point was the reason for why it was so hard to make the issue reproducible. I had to research the basics about how the renderer works, so let's take a short tour about: The canvas elementBoth the Canvas and the WebGL renderer rely on HTML's Zooming the canvas element: problem and workaroundWhen a canvas object is zoomed, it produces aliasing effects, due to the nature of pixel graphics. It's just the same as zooming into a photo. The xterm.js renderer works around this using the following steps:
Let's use the following symbols to describe the behaviour mathematically:
The scaled canvas size
So the terminal is painted on
The browser then zooms in:
i.e. the width that we see is exactly the width that we painted. Why the workaround does not guarantee a crisp presentationIn theory and in a continouus scale the above workaround works great. However, since a screen has a limited number of pixels, we only have a discrete number of possible values for Let's see two examples:
I came across this by analyzing the canvas element in the DOM, where the canvas size and the display size can be directly seen: <canvas width="119" height="119" style="width: 95px; height: 95px;"></canvas> (Disclaimer: This is an artificial example in order to match my mathematical example above. In the real world I wasn't able to produce these exact sizes with xterm.js, but I had several other combinations of canvas width and style width leading to the same result.) The issue now gets even more clear when we calculate the zoom factor
It is obvious that the scaling factor which we actually used to downscale the canvas to its CSS width is not equal to the scaling factor the browser uses to upscale the site. It's exactly these cases where we're getting the blurry terminal lines. I checked cases without rounding errors (like the 100px and 125% zoom above) and in those cases all characters are displayed equally throughout the whole line. Possible solutionWe could choose the CSS width This will result in blank spaces around the terminal, the maximum size of which depends on the denominator of the zoom factor Example:
So if we reduce our visual representation of the canvas in the latter example from 95px to 92px and add a 3px wide empty space on one side, we would be able to paint on a canvas of 92px * 1.25 = 115px, which is an integer and thus does not lead to a slightly off zoom factor. Drawbacks of the solutionThe difference calculated in the above example is the maximum difference that can occur with this given zoom factor, since the next higher number would be 96, which is again integrally divisible by 4. Generally speaking:
So with a zoom factor of 125%, the empty border will never be wider than 3px. Personally I would trade off 3px of my monitor for a crisp presentation. With more sophisticated zoom levels this might get worse, though. If someone sets a zoom level of 101% for example (that would be 101/100 in fractional), we have a denominator of 100, meaning that in the worst case we would add 99px of empty space in order to have a crisp presentation. I'm not sure if this is still a good tradeoff. Bottom lineA good compromise might be to define a maximum width of empty space we would accept when aiming for a crisp presentation. If we would have to add more than this maximum, then fall back to the whole width with blurry rendering.
Browsers generally allow any zoom level, but pre-defined values e.g. in Firefox are limited as well:
So perhaps we can agree that we would cover the vast majority of use cases by supporting the above listed zoom levels. That would mean that the maximum supported denominator would be 10, resulting in a maximum addition of empty space of 9px. I would assume that everybody would trade 9px of their monitor space if the alternative is a blurry font. Still the 9px are not lost, you could resize your terminal accordingly so that other parts around it get more space. However, I am not sure about this (there's always someone to complain ;-)), so we might make this a configurable option. Let me know what you think. |
Addendum: I don't know if it's particularly reproducible in other environments, but in my Firefox running the xterm.js demo with the default settings (font |
IMO trading a few pixels of padding for clear font rendering is a perfectly reasonable thing to do. |
@carlfriedrich Awesome work! Are you preparing a PR? |
@pasqualirb Thank you. I'm actually not familiar with the code, I just analyzed the problem to have it reproducible. So I'm hoping for some profound opinion from @Tyriar on this topic. |
ping @Tyriar |
Sorry about the delay, I haven't reviewed the proposal thoroughly as I'm a bit swamped after a long break from work, but I agree 100% with @Eugeny if it does indeed fix the problem.
|
@Tyriar Thanks for your comment. If you like, I can offer to help with the implementation, but I would need some guidance for the code base first. |
@carlfriedrich sure! Each renderer has a method to calculate the dimensions. Here's the canvas renderer: xterm.js/src/browser/renderer/Renderer.ts Lines 195 to 206 in cae9f44
And webgl renderer: xterm.js/addons/xterm-addon-webgl/src/WebglRenderer.ts Lines 456 to 467 in 1e89016
I think those are the main places you would need, let me know if you need any other pointers. |
This uses the ResizeObserver devicePixelContentBoxSize API in order to fetch the exact device pixel dimensions from the browser. The old possibly blurry behavior is used as a fallback if that API is not available. Part of xtermjs#2662 Part of microsoft/vscode#85154
@carlfriedrich thanks a lot for the investigation, I spent quite a while adapting your proposal and it turns out it didn't end up working. However, I did find another solution using a fairly recent web API that wasn't around when the renderers were initially created! #3926 |
#985 (comment)
Right now the canvas size is changed based on
window.devicePixelRatio
, one idea is to disable this type fo scaling (see how VS Code's minimap doesn't change when zooming) and then scale manually by applying a multiplier to relevant numbers.Applies to WebGL and canvas renderers.
The text was updated successfully, but these errors were encountered: