-
Notifications
You must be signed in to change notification settings - Fork 20
add fractional scale protocol support #63
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
base: master
Are you sure you want to change the base?
Conversation
ii8
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This segfaults if I change my output scale. For example if I set my output scale factor to 2 and restart my monitor, havoc crashes. At a glance I'm not sure what the cause is, but presumably a use after free with the buffers or font.
main.c
Outdated
| int32_t width, int32_t height, struct wl_array *state) | ||
| { | ||
| if (!term.scale) | ||
| fs_preferred_scale(NULL, NULL, 240); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the default preferred scale 2?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for spotting this. It should be 120, I was just testing it with 240 to ensure other default scales work correctly. I will change it to 120 on the next re-submission of this pull request.
main.c
Outdated
| buffer_unmap(buf); | ||
| if (buffer_init(buf) < 0) | ||
| abort(); | ||
| fprintf(stderr, "buffer_init failed\n"), abort(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
buffer_init already prints an error when it fails, this doesn't really add anything useful
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noted, thanks for the clarification.
main.c
Outdated
| { | ||
| int x = (wl_fixed_to_double(term.ptr_x) - term.margin.left) / term.cwidth; | ||
| double dx = wl_fixed_to_double(term.ptr_x) - term.margin.left; | ||
| int x = dx * term.scale / (120 * term.cwidth); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not return the correct coordinates because the margin is already scaled
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for picking up this issue, new pull request will avoid re-scaling the margin.
main.c
Outdated
| { | ||
| int y = (wl_fixed_to_double(term.ptr_y) - term.margin.top) / term.cheight; | ||
| double dy = wl_fixed_to_double(term.ptr_y) - term.margin.top; | ||
| int y = dy * term.scale / (120 * term.cheight); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see above
main.c
Outdated
| if (term.scale != s && s > 0) { | ||
| term.scale = term.vp ? s : (s + 119) / 120 * 120; | ||
| if (term.cheight) | ||
| font_deinit(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This unnecessarily unmaps the whole font file. Only the cached glyphs need to be re-rendered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noted, but is there a way to invalidate the cached glyphs without using "delete_cache(font.cache)" (which will access the presumably private cache field of the font struct)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way to do it is probably to add a font_resize function that will delete the old cache, change the font size values and reinitialise the cache.
Ideally the font code should be changed so that multiple fonts can be loaded at once so that for example a bold font can be used in addition to the normal font, but I suppose that's not strictly needed for this fractional scaling.
Is this here not the cause of the segfault too, because the font size gets changed but there's no guarantee that the buffers will be resized before the next frame callback.
| return; | ||
|
|
||
| term.width = term.confwidth; | ||
| term.height = term.confheight; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will allocate oversized buffers when the margin is disabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will send a new pull request which will not oversize the buffers (adds a couple of lines of extra code in complexity but avoids wasting memory on larger buffers)
main.c
Outdated
| { | ||
| if (strcmp(i, "wl_compositor") == 0) { | ||
| term.cp = wl_registry_bind(r, id, &wl_compositor_interface, 1); | ||
| term.cp = wl_registry_bind(r, id, &wl_compositor_interface, 3); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, we might still want to support older compositors, will need to look into it.
Can I check which compositor you are using? I have so far tested on dwl and labwc, and also tinywl (a compositor not supporting viewporter/fractional scale, to test the standard 1.0x scaling). Also, I'll look into whether fs_preferred_scale should set term.configured = false - as there is a theoretical possibility a compositor can send fractional_scale events without a toplvl_configure event. (This means fs_preferred_scale can change the glyph dimensions without ensuring recalculation of rows & cols). |
|
I'm on sway |
I've just now done testing with sway 1.11, compiled using default options and changing the scale (via editing sway config file and reloading using mod + shift + c and also via wlr-randr) but can't trigger any segfaults. Which version of sway are you using, and do you have gdb? If so, if you compile havoc-fs using -g, and run gdb within either a non-fractionalscale havoc or another terminal (so you can see gdb output), gdb should help with finding out where the segfault is occurring. I have also added a wl_surface_commit() to the fractional scale event handler function, so this ensures that wayland will call toplvl_configure event for every change in fractional scale. I'll repost the new pull request soon. |
main.c
Outdated
| exit(1); | ||
| } | ||
| if (term.surf) | ||
| wl_surface_commit(term.surf); /* trigger toplvl_configure event */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fs_preferred_scale is called from toplvl_configure in the default case, and now we want to trigger toplvl_configure from fs_preferred_scale, something is confused here.
I also don't see why committing the surface here would necessarily cause a configure event.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
toplvl_configure calls fs_preferred_scale only in the case where a fractional scale hasn't been set yet, and this guards against the case where toplvl_configure events occurs before the 1st fractional scale event. On the other hand, fs_preferred_scale doesn't directly call toplvl_configure, it relies on the compositor to do it after a fractional scale change has occurred and after the current surface is committed. (Hence where a fractional scale change hasn't occurred there won't be an endless cycle between these two functions).
The word "trigger" in the comment is my own choice wording, but comes from my understanding of the surface lifecycle (https://wayland-book.com/surfaces-in-depth/lifecycle.html). The fractional scale event isn't covered in the Wayland Book, but the Book does state that the current scale (integer and resumably also fractional) is part of the current surface state.
My understanding is that Wayland isn't able to sending any configure events until the current surface (which was drawn or part-drawn before the fractional scale event occured) is flushed out. Once the current surface is flushed, Wayland is then able to reconfigure for the new scaling geometry, including sending out the relevant configure events.
Unfortunately, fractional_scale_v1 has very little documentation on this, and so it might be necessary to test with various compositors, to verify their behaviour against the above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid any risk of non-atomic changes to font size vs dimensions, I have now changed fs_preferred_scale to store to a new variable, term.confscale. Only when toplvl_configure is called will it now take the preferred scale, resize the font and recalculate the buffer dimensions and make the new scale active. A new pull request (#3) will have this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not how it works, the preferred_scale event is not part of the surface configuration state managed by the configure event, so the new scale can just be used in the next committed buffer. The book is talking about the scale the client has set for a surface, which is double buffered, not the preferred scale event.
Calling wl_surface_commit will not necessarily cause the compositor to send a configure event here.
|
|
||
| /* Draw source data only into subset of buffer for fractional scale */ | ||
| if (term.vp && term.surf) { | ||
| wl_surface_set_buffer_scale(term.surf, m); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do this? We are using viewport to scale it no?
| term.confwidth = width ? width : term.cfg.col * term.cwidth; | ||
| term.confheight = height ? height : term.cfg.row * term.cheight; | ||
|
|
||
| /* Draw source data only into subset of buffer for fractional scale */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not create the correct size buffer and use the whole buffer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a limitation of the viewporter protocol, which is overly conservative in it's bound checking and requires the source buffer to be sized to the (ceiling) integral multiple of the destination size, even when only a subset of this buffer is used. In previous versions of havoc-fs (not committed to my fork's github repo), I did try using a fractionally sized source buffer but viewporter reports an out of bounds error. The fractional scale protocol appears to only be used by Wayland to improve image quality (but not to reduce memory usage) compared to integer scaling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the buffer can be any size, something else must have been the cause of the error you saw, this unnecessarily uses up to 4x more memory for the buffers.
| if (term.vp && term.surf) { | ||
| wl_surface_set_buffer_scale(term.surf, m); | ||
| wp_viewport_set_destination(term.vp, w, h); | ||
| wp_viewport_set_source(term.vp, 0, 0, w*f/(120*m), h*f/(120*m)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above
| if (!term.scale) | ||
| fs_preferred_scale(NULL, NULL, 120); | ||
|
|
||
| /* w x h = logical dimensions, s = fract scale, m = buffer scale */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why have an integer buffer scale, can't we just create the buffer with the correct fractional size?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a limitation of the viewporter protocol. I don't know enough about the internal implementation of viewporter to add any further detail, but it might have something to do with Wayland's history of only supporting integer scaling before then adding fractional scaling. Or it might be to prevent multiple buffer memory resizing occurring in response to the 'zoom' level changing by tiny amounts at a time (eg. in a fancy UI with zoom animations, maybe there might a use case of viewporter being used to draw the various levels of zoom - and by having an over-scaled buffer, it means that only the pixels inside the buffer need to be redrawn, and the buffer memory size itself doesn't need to change). This is all speculation on my part, as no zoom animation is done currently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not how it works, the buffer can be any size.
| term.width = col * term.cwidth; | ||
| term.height = row * term.cheight; | ||
| term.margin.left = (term.confwidth/m * s - col * cw) / (2*120); | ||
| term.margin.top = (term.confheight/m * s - row * ch) / (2*120); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why scale back and forth here, isn't it all in buffer coordinates?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use confwidth & confheight to communicate window dimensions between the two configure functions. The question is then whether to pre-multiply by the buffer scale or not. This is an arbitrary choice but is done in integer arithmetic (hence is exact, with no rounding errors upon dividing the buffer scale back out).
The other choice could have been not to pre-multiply by buffer scale but I went with pre-multiplying because in nearly all cases where there is a division by m, there is also multiplication by s or division by cw or ch, so this has the effect of encapsulating the scale calculations to fewer lines of code. In contrast, if I didn't pre-multiply, then many simple assignment lines (eg. term.width = term.confwidth) became cluttered up by scaling calculations, and then almost every line of code involving dimensions ends up also needing either a 'm' or a 's' scale factor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just use the actual size of the terminal here so it's all in buffer coordinates, you don't need confwidth. Without scaling they were equal so it didn't matter before.
main.c
Outdated
|
|
||
| /* Logical dimensions >= 2 * font sz & snap to grid if no margin */ | ||
| w = w * s > cw * 2 ? w : cw * 2; | ||
| h = h * s > ch * 2 ? h : ch * 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This ensures the buffer is never smaller than 2 glyphs high & wide. The choice of two is a bit arbitrary but buffer should never be smaller than 1 glyph as otherwise it gets rounded down to zero, which causes segfaults. Looking at it again, the minimum size (3rd operand of conditional operator) should be cw * 2 / s, not cw * 2. I will resumbit the pull request for this once any other changes need have all been decided. The other change to consider is whether the following in configure() is no longer needed due to the 2x2 glyph minimum:
if (col == 0 || row == 0)
return;
I'm on sway 1.11. gdb does not reveal a consistent source location where the segfault happens unfortunately. I can cause the segfault by using |
|
Another thing I wonder: is there any point in supporting the wl_surface integer scaling method at all? Compositor scaling only is probably good enough for when fractional scaling is not available. Does it make sense to have 3 different scaling methods? |
Actually, of the 3 options (no scaling, integer scaling by receiving wl_output events, and receiving preferred fractional scaling events), I chose to omit the middle one (wl_output scaling). wl_output scale has a different scale for each monitor, and if we were to handle dynamic re-scaling (ie. new monitor added with a different scale, or change of scale for existing monitor) it would be the most complex option (eg. consider what happens if a surface moves from one monitor to another monitor with different scale). You could argue the above is a strawman case, and the steelman case is to calculate a single preferred integer scale to use across all monitors. But if we are doing that, then it is actually cleaner to just use the the preferred fractional scale protocol to effectively calculate a single scale for all monitors for me, rather than checking the scale of each individual monitor myself. Also, note that integer scaling wastes just as much memory (because scale is rounded up to nearest integer) as the current inefficient/over-conservative viewporter bounds checking for fractional scaling. |
I am now running 'swaymsg -t command output eDP-1 scale 2' and it rescales immediately without needing to restart my monitor. What do you do to restart your monitor (and do you use more than 1 monitor)? Also, does havoc rescale work if you put a line in your swayconfig like the following, start sway and then change the scale value in the config file with a text editor to a new value (eg. 2.0), and trigger a config reload with MOD + shift + c? output * scale 1.4 Can you show the sway verbose output (using -d on starting up sway)? For example, this is what I get when I run swaymsg on X11 backend: |
…side toplvl_configure event
|
I think you've gotten a few things confused here, I'm not sure why most of the maths you are doing is really necessary. Here is roughly what I think would be needed for fractional scaling: 4654464 I haven't tested this so it might not work properly but it should show what I mean. |
|
dwl sends the preferred scale event before the surface is configured. fix: 4211859 |
|
I tested on labwc and dwl, and fonts are being rendered well. |
|
I can't remember exactly, the buffer size depends on your font anyway so it's hard to replicate, but the larger the scale factor the more it happens. You can set scale to 2x, set |
It get good font rendering no matter what dimensions I resize the havoc window to on sway, dwl and on labwc (amdgpu graphics driver in all 3 cases). Do you have any guess on what is causing it in your case? |



On compositors supporting the fractional scale and viewporter protocols, scales the terminal font size to reflect the fractional scale setting. On compositors not supporting these protocols, scaling is set to 1.0x. If fractional scale is changed dynamically (eg. using wlr-randr), de-init of the old font size and init of the new font size occurs in response to fractional scale events.