Merge pull request '"Native resolution" streamed the compositor's points, not the panel's pixels — and the window was never high-DPI either' (#112) from worktree-wayland-native-pixel-density into main
ci / bun-nix (push) Successful in 26s
ci / docs-site (push) Successful in 1m4s
android / android (push) Canceled after 1m19s
ci / web (push) Successful in 1m12s
apple / swift (push) Canceled after 1m24s
apple / screenshots (push) Canceled after 0s
arch / build-publish (push) Canceled after 1m31s
ci / rust (push) Canceled after 1m35s
ci / rust-arm64 (push) Canceled after 1m34s
deb / build-publish (push) Canceled after 1m15s
deb / build-publish-host (push) Canceled after 37s
deb / build-publish-client-arm64 (push) Canceled after 25s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 17s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 4s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Canceled after 0s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
flatpak / build-publish (push) Canceled after 4s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 7s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 1s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Canceled after 1m45s
windows-msix / package (x64, , x86_64-pc-windows-msvc, C:\t) (push) Canceled after 0s
windows / build (aarch64-pc-windows-msvc) (push) Canceled after 0s
windows / build (x86_64-pc-windows-msvc) (push) Canceled after 0s

Reviewed-on: #112
This commit was merged in pull request #112.
This commit is contained in:
2026-08-08 10:09:55 +00:00
+91 -6
View File
@@ -550,7 +550,15 @@ fn run_inner(mut opts: SessionOpts, mut mode: ModeCtl) -> Result<Option<Outcome>
Some((x, y)) => b.position(x, y),
None => b.position_centered(),
};
b.resizable().vulkan();
// HIGH_PIXEL_DENSITY: give us a backbuffer in the panel's REAL pixels. Without it
// SDL leaves the Wayland surface at buffer scale 1, so on a fractionally scaled
// output (KDE at 150 %: a 2560×1600 panel reported as 1707×1067 points) the
// swapchain is built at 1707×1067 and the compositor upscales it to the glass —
// a 2560×1600 stream is resampled DOWN and back UP, and looks it. The flag only
// widens `size_in_pixels()`; `size()` stays logical, which is what the persisted
// window size and SDL's own mouse coordinates are in, and both callers already
// use the right one.
b.resizable().vulkan().high_pixel_density();
if opts.fullscreen {
b.fullscreen();
}
@@ -644,11 +652,11 @@ fn run_inner(mut opts: SessionOpts, mut mode: ModeCtl) -> Result<Option<Outcome>
let native = window
.get_display()
.and_then(|d| d.get_mode())
.map(|m| Mode {
width: m.w.max(0) as u32,
height: m.h.max(0) as u32,
refresh_hz: m.refresh_rate.round().max(0.0) as u32,
})
.map(|m| native_mode(m.w, m.h, m.pixel_density, m.refresh_rate))
.ok()
// A zero-sized mode is as useless as no mode at all — only `Err` used to reach
// the fallback, so a display that reported 0×0 streamed a 0×0 request.
.filter(|m: &Mode| m.width > 0 && m.height > 0)
.unwrap_or(Mode {
width: 1920,
height: 1080,
@@ -2136,6 +2144,37 @@ fn run_inner(mut opts: SessionOpts, mut mode: ModeCtl) -> Result<Option<Outcome>
Ok(outcome)
}
/// An `SDL_DisplayMode` as the panel's REAL pixels — the `0 = native` stream mode.
///
/// SDL3 reports a display mode in SCREEN COORDINATES, not pixels, and hands you the ratio
/// between the two separately as `pixel_density`. On X11 and Windows that ratio is always
/// 1.0 (SDL never sets it there, and `SDL_video.c` normalizes the unset 0.0 up to 1.0), so
/// this is a no-op — but under a Wayland compositor doing FRACTIONAL scaling it is the
/// whole ballgame: KDE at 150 % advertises a 2560×1600 panel as 1707×1067 points with
/// `pixel_density` ≈ 1.4997, and taking `m.w`/`m.h` raw is what made "Native resolution"
/// negotiate 1706×1066 (1707×1067 even-floored by `render_scale::apply`) and stream a
/// blurry two-thirds-size image. `SDL_VIDEO_WAYLAND_SCALE_TO_DISPLAY=1` is the same fix
/// from the outside — it makes SDL report the native mode itself — which is why setting it
/// was a workaround.
///
/// The density is the exact `pixels / points` ratio SDL derived from the output, so the
/// multiplication recovers the panel size to the pixel rather than approximating it.
fn native_mode(w: i32, h: i32, pixel_density: f32, refresh_rate: f32) -> Mode {
// A non-finite or non-positive density is SDL telling us nothing useful; 1× at least
// preserves the pre-fix behaviour instead of collapsing the mode to zero.
let density = if pixel_density.is_finite() && pixel_density > 0.0 {
pixel_density
} else {
1.0
};
let px = |v: i32| (v.max(0) as f32 * density).round().max(0.0) as u32;
Mode {
width: px(w),
height: px(h),
refresh_hz: refresh_rate.round().max(0.0) as u32,
}
}
/// Match-window (D1): replace the params' requested w/h with the window's physical pixel
/// size — even-floored (the host's `validate_dimensions` rejects odd) and clamped to a
/// sane minimum — keeping the resolved refresh. Under `--fullscreen` the window IS the
@@ -2908,6 +2947,52 @@ fn stats_text(
mod tests {
use super::*;
/// The field report this exists for: CachyOS/KDE Plasma 6.7.4 Wayland, a 2560×1600@165
/// laptop panel at 150 % scaling. KDE advertises the output as 1707×1067 points, SDL
/// hands that back as the desktop mode with `pixel_density` = 2560/1707, and "Native
/// resolution" streamed 1706×1066 — the points, even-floored by `render_scale::apply`.
#[test]
fn native_is_the_panels_pixels_under_fractional_wayland_scaling() {
// SDL derives the density as the exact pixels-per-point ratio of the output.
let density = 2560.0 / 1707.0;
let m = native_mode(1707, 1067, density, 165.0);
assert_eq!((m.width, m.height, m.refresh_hz), (2560, 1600, 165));
// …and it survives the even-floor the host's `validate_dimensions` forces, which is
// where 1707×1067 lost its odd pixel and became the reported 1706×1066.
assert_eq!(
punktfunk_core::render_scale::apply(m.width, m.height, 1.0, 8192),
(2560, 1600)
);
assert_eq!(
punktfunk_core::render_scale::apply(1707, 1067, 1.0, 8192),
(1706, 1066),
"the pre-fix mode, kept here so the regression is legible"
);
}
#[test]
fn native_is_unchanged_where_the_density_is_one() {
// X11, Windows, and Wayland at 100 % all report 1.0 — the fix must be inert there.
let m = native_mode(2560, 1600, 1.0, 165.0);
assert_eq!((m.width, m.height, m.refresh_hz), (2560, 1600, 165));
// Integer scaling (a 200 % 4K panel reported as 1920×1080 points) doubles cleanly.
let m = native_mode(1920, 1080, 2.0, 60.0);
assert_eq!((m.width, m.height), (3840, 2160));
}
#[test]
fn a_nonsense_density_falls_back_to_one_rather_than_zeroing_the_mode() {
// SDL normalizes an unset density to 1.0, but this must not be the one place a
// driver quirk can hand the host a 0×0 mode request.
for bogus in [0.0, -1.0, f32::NAN, f32::INFINITY] {
let m = native_mode(2560, 1600, bogus, 60.0);
assert_eq!((m.width, m.height), (2560, 1600), "density {bogus}");
}
// A negative mode size is clamped, not wrapped into a huge u32.
let m = native_mode(-1, -1, 1.5, 60.0);
assert_eq!((m.width, m.height), (0, 0));
}
#[test]
fn overlay_scale_follows_dpi_and_survives_a_bogus_display() {
// 100 % / 96 dpi is the identity — the chrome keeps the size it always had.