Skip to content

Fix editor scaling/size issues on Windows#10

Closed
emuell wants to merge 2 commits intoilmai:mainfrom
emuell:fix/windows-scaling-issues
Closed

Fix editor scaling/size issues on Windows#10
emuell wants to merge 2 commits intoilmai:mainfrom
emuell:fix/windows-scaling-issues

Conversation

@emuell
Copy link
Contributor

@emuell emuell commented Mar 6, 2026

VST3 and Clap plugins currently open with wrong sizes (report a wrong size to the host) on systems with non-100% scaling settings. e.g. the window is half the size it should be on 4K monitors with a scaling of 200%:

  • Windows sizes, as reported to hosts, should be physical sizes, not logical sizes on Windows. This is ugly, but that's the way it is with Clap and VST3 on Windows from what I know and tested.
  • To deal with that it must be possible to:
    1. query the default scaling, Window::screen_scale here, in case there's no window to query the size from yet, when the host asks for the plugin's window size before opening the plugin window.
    2. when the plugin window is open, EditorHandle::scale should be used (was os_scale before), as windows on Windows may have different effectively applied scaling, depending on which monitor they are visible.

The real fix is here:

impl Editor { 
    /// [...]
    
    /// Returns current window size
    fn window_size(&self) -> (f64, f64) {
        if cfg!(target_os = "windows") {
            // on windows, window sizes are physical sizes 
            let (width, height) = Self::DEFAULT_SIZE;
            let scale = self.scale();
            (width * scale, height * scale)
        }
        else {
            Self::DEFAULT_SIZE
        }
    }

Plus the impls for EditorHandle::scale and Window::screen_scale.

The rest tries making the screen_scale and scale available to Editor. I'm not very familiar with the library, so there are likely easier and cleaner ways to do this. Please let me know if you can think of a better way of dealing with this.

Ideally, the default impl of Editor should deal with all that stuff. Right now the fix assumes that scale is implemented correctly and check_window_size and set_window_size need to adjust sizes for scaling manually too when implemented. But this is also outside the scope of this PR, at least for me, and likely requires a totally different Editor handling.

Note: A similar fix may be necessary on Linux, but I haven't tested this yet, and also haven't implemented the Linux parts, so this only works for Windows. On macOS window sizes are logical sizes, so things should work as before here.

Also note that this doesn't address cases where scaling changes when a plugin window gets moved from one monitor to another with different DPI settings. The behavior of each host is different here so this is difficult to get right and should be addressed in a separate issue.

emuell and others added 2 commits March 5, 2026 14:57
- Add Window scale accessors to plugin editors and window adapters
- Add new Window `screen_scale` functions, which can be used when the host queries the plugin's window size, and there's no window created yet
@ilmai
Copy link
Owner

ilmai commented Mar 6, 2026

Thanks for your contribution! I actually didn't notice this on my Windows machine but it only has a scaling factor of 150%. I'll have to look through this quite carefully as scaling has been quite a headache to get consistenly working across platforms especially with all the corner cases such as drag & drop, resizeable windows etc. I'll check how this works with my commercial plugin using plugin-things and also check the situation in Linux. I'll then either comment on the PR or work on top of it. Again, thanks!

@emuell
Copy link
Contributor Author

emuell commented Mar 6, 2026

Nothing to thank for! I'm happy that I can build stuff thanks to your work, which saves me the hassle of writing all that platform stuff by myself :)

Yes, all this scaling stuff is a mess on Windows. Unfortunately, it won't get much better on Linux either. The only reliable way I'm aware of to get the scaling there in a portable way is using XGetDefault(display, "Xft", "dpi"); followed by (DisplayHeight(display, DefaultScreen(display)) * 25.4) / DisplayHeightMM(display, DefaultScreen(display)) as fallback. The proper "modern" way would be using xrandr to support multiple monitors with different resolutions, but that's event more hairy - especially in a plugin context.

The most foolproof way to create a plugin GUI that works for everyone on Linux is to allow the user to set a custom scale in the plugin UI and just use a good guess as the default. Actually it would be neat if Editor could do that automatically: Adjusting the Slint backend's scale dynamically to fit new sizes when resizing the window - as a default set_size implementation.

Either way, let me know if you would like me to provide any further feedback or assistance here with testing or finishing up this PR.

Comment on lines +116 to +127
fn sceen_scale() -> f64 {
if let Some(main_thread_marker) = MainThreadMarker::new() {
if let Some(screen) = NSScreen::mainScreen(main_thread_marker) {
return screen.backingScaleFactor()
}
}
else {
#[cfg(debug_assertions)]
panic!("Calling screen_scale from an unexpected thread");
}
1.0
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about to simplify this to:

Suggested change
fn sceen_scale() -> f64 {
if let Some(main_thread_marker) = MainThreadMarker::new() {
if let Some(screen) = NSScreen::mainScreen(main_thread_marker) {
return screen.backingScaleFactor()
}
}
else {
#[cfg(debug_assertions)]
panic!("Calling screen_scale from an unexpected thread");
}
1.0
}
fn screen_scale() -> f64 {
let Some(main_thread_marker) = MainThreadMarker::new() else {
debug_assert!(false, "Calling screen_scale from an unexpected thread");
return 1.0;
};
NSScreen::mainScreen(main_thread_marker)
.map_or(1.0, |screen| screen.backingScaleFactor())
}

@ilmai
Copy link
Owner

ilmai commented Mar 12, 2026

After giving this some thought, I'm not convinced this should be handled automatically in plugin-things. I've updated the gain example with the intended way of handling set_scale() based on what I'm doing in my commercial plugin. So instead of the OS scale getting "baked in" it's used as a suggested scale by the plugin which the plugin can accept or ignore. Let me know what you think.

@emuell
Copy link
Contributor Author

emuell commented Mar 12, 2026

That surely would be an elegant way to avoid most of the ugly OS parts, but I can't make that work here.

Using the updated gain example from the master branch on Windows:

  • Live: plugin always shows up too small (without "Auto-Scale Plugin Window" option disabled)
  • Bitwig (Clap & VST3): Initially has the right size, when opening the editor again it's too small
  • Renoise: correct scaling but wrong window size

If you go this way, then I think we definitely need a good default scaling (something like screen_scale in my PR instead of using 1.0). And set_scale likely must also update the scaling of already opened editors. Some hosts may set the scaling before opening the editors, others afterwards, others not at all.

I though this should fix the above mentioned issue, but it's causing other problems on Windows:

    fn set_scale(&mut self, scale: f64) {
        self.scale = scale;
        if let Some(editor_handle) = self.editor_handle.as_ref() {
            editor_handle.set_scale(scale);
        }
    }

Maybe the window needs here needs to be resized as well then or the host must be notified of the window size?

How did you solve those issues in your plugin?

@ilmai
Copy link
Owner

ilmai commented Mar 12, 2026

Ah yes, I forgot a crucial part in set_scale() where it passes forward the new scale & size to Slint.

Curiously it still worked correctly in Bitwig and Live (not sure why you saw different behavior) but was wrong in Reaper. I don't have Renoise.

Just committed a fix.

@ilmai
Copy link
Owner

ilmai commented Mar 12, 2026

Also: default scaling comes specifically from set_scale() and seems to match the OS scale (150% on my system, presumably 200% on yours).

@emuell
Copy link
Contributor Author

emuell commented Mar 12, 2026

Yes, this works now in Live, Bitwig and Renoise now, but still doesn't work in hosts that don't set a scaling at all, e.g. Bidule. There may be others as well. Or what do you mean with "default scaling comes specifically from set_scale()"? This only relies on the host now, doesn't it?

As mentioned previously, this could be resolved by initializing the editor's scale to match that of the default monitor. Initially using the "correct" scaling will also avoid an extra window resize here.

@ilmai
Copy link
Owner

ilmai commented Mar 12, 2026

My opinion is that this shouldn't be default behavior in the plugin framework since a correctly behaving host should call set_scale() / setContentScaleFactor() which ends up calling Editor::set_scale(). This is exactly the reason this interface exists since the host has better information about the main window scale than the plugin does.

@emuell
Copy link
Contributor Author

emuell commented Mar 12, 2026

Okay. But scaling setters are optional for VST3 - not sure how Clap handles this.

Scaling will work in most hosts now, so I guess that's good enough for a start!

@emuell emuell closed this Mar 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants