diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1b92632ed..5e87b1c66 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,7 +71,7 @@ jobs: toolchain: [stable] include: - os: ubuntu-latest - toolchain: "1.66.0" + toolchain: "1.67.0" - os: ubuntu-latest toolchain: beta diff --git a/crates/kas-core/Cargo.toml b/crates/kas-core/Cargo.toml index 75f0de426..6429c27a8 100644 --- a/crates/kas-core/Cargo.toml +++ b/crates/kas-core/Cargo.toml @@ -96,7 +96,7 @@ ron = { version = "0.8.0", package = "ron", optional = true } toml = { version = "0.8.2", package = "toml", optional = true } num_enum = "0.7.0" dark-light = { version = "1.0", optional = true } -raw-window-handle = "0.5.0" +raw-window-handle = "0.6.0" async-global-executor = { version = "2.3.1", optional = true } cfg-if = "1.0.0" smol_str = "0.2.0" @@ -123,4 +123,4 @@ version = "0.5.0" # used in doc links version = "0.29.2" optional = true default-features = false -features = ["rwh_05"] +features = ["rwh_06"] diff --git a/crates/kas-core/src/draw/draw_shared.rs b/crates/kas-core/src/draw/draw_shared.rs index 469c57488..3d9fe20bd 100644 --- a/crates/kas-core/src/draw/draw_shared.rs +++ b/crates/kas-core/src/draw/draw_shared.rs @@ -149,6 +149,9 @@ impl DrawShared for SharedState { pub trait DrawSharedImpl: Any { type Draw: DrawImpl; + /// Get the maximum 2D texture size + fn max_texture_dimension_2d(&self) -> u32; + /// Set font raster config fn set_raster_config(&mut self, config: &RasterConfig); diff --git a/crates/kas-core/src/shell/common.rs b/crates/kas-core/src/shell/common.rs index 8e2bac277..127a2e755 100644 --- a/crates/kas-core/src/shell/common.rs +++ b/crates/kas-core/src/shell/common.rs @@ -10,6 +10,7 @@ use crate::draw::{color::Rgba, DrawIface, WindowCommon}; use crate::geom::Size; use crate::theme::Theme; use raw_window_handle as raw; +use std::time::Instant; use thiserror::Error; /// Possible failures from constructing a [`Shell`](super::Shell) @@ -194,12 +195,11 @@ pub trait WindowSurface { type Shared: kas::draw::DrawSharedImpl; /// Construct an instance from a window handle - fn new( - shared: &mut Self::Shared, - size: Size, - window: W, - ) -> Result + /// + /// It is required to call [`WindowSurface::do_resize`] after this. + fn new(shared: &mut Self::Shared, window: W) -> Result where + W: raw::HasWindowHandle + raw::HasDisplayHandle, Self: Sized; /// Get current surface size @@ -220,5 +220,7 @@ pub trait WindowSurface { fn common_mut(&mut self) -> &mut WindowCommon; /// Present frame - fn present(&mut self, shared: &mut Self::Shared, clear_color: Rgba); + /// + /// Return time at which render finishes + fn present(&mut self, shared: &mut Self::Shared, clear_color: Rgba) -> Instant; } diff --git a/crates/kas-core/src/shell/mod.rs b/crates/kas-core/src/shell/mod.rs index e6af7855b..c57cf7bb4 100644 --- a/crates/kas-core/src/shell/mod.rs +++ b/crates/kas-core/src/shell/mod.rs @@ -53,6 +53,7 @@ enum ProxyAction { #[cfg(test)] mod test { use super::*; + use std::time::Instant; struct Draw; impl crate::draw::DrawImpl for Draw { @@ -152,6 +153,10 @@ mod test { impl crate::draw::DrawSharedImpl for DrawShared { type Draw = Draw; + fn max_texture_dimension_2d(&self) -> u32 { + todo!() + } + fn set_raster_config(&mut self, _: &crate::theme::RasterConfig) { todo!() } @@ -224,12 +229,9 @@ mod test { impl WindowSurface for Surface { type Shared = DrawShared; - fn new( - _: &mut Self::Shared, - _: crate::prelude::Size, - _: W, - ) -> Result + fn new(_: &mut Self::Shared, _: W) -> Result where + W: raw_window_handle::HasRawWindowHandle + raw_window_handle::HasRawDisplayHandle, Self: Sized, { todo!() @@ -254,7 +256,7 @@ mod test { todo!() } - fn present(&mut self, _: &mut Self::Shared, _: crate::draw::color::Rgba) { + fn present(&mut self, _: &mut Self::Shared, _: crate::draw::color::Rgba) -> Instant { todo!() } } diff --git a/crates/kas-core/src/shell/shared.rs b/crates/kas-core/src/shell/shared.rs index 22227cbb4..6d462f260 100644 --- a/crates/kas-core/src/shell/shared.rs +++ b/crates/kas-core/src/shell/shared.rs @@ -39,7 +39,6 @@ pub(crate) struct SharedState, pub(super) data: Data, /// Estimated scale factor (from last window constructed or available screens) - pub(super) scale_factor: f64, options: Options, } @@ -82,7 +81,6 @@ where window_id: 0, }, data, - scale_factor: pw.guess_scale_factor(), options, }) } diff --git a/crates/kas-core/src/shell/shell.rs b/crates/kas-core/src/shell/shell.rs index cc20246fb..d23375d0d 100644 --- a/crates/kas-core/src/shell/shell.rs +++ b/crates/kas-core/src/shell/shell.rs @@ -262,17 +262,6 @@ impl<'a> PlatformWrapper<'a> { // Otherwise platform is unsupported! } - /// Guess scale factor of first window - pub(super) fn guess_scale_factor(&self) -> f64 { - if let Some(mon) = self.0.primary_monitor() { - return mon.scale_factor(); - } - if let Some(mon) = self.0.available_monitors().next() { - return mon.scale_factor(); - } - 1.0 - } - /// Create a waker /// /// This waker may be used by a [`Future`](std::future::Future) to revive diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index c117b331f..c82bcf02f 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -8,8 +8,8 @@ use super::common::WindowSurface; use super::shared::{SharedState, ShellShared}; use super::ProxyAction; -use kas::cast::Cast; -use kas::draw::{color::Rgba, AnimationState}; +use kas::cast::{Cast, Conv}; +use kas::draw::{color::Rgba, AnimationState, DrawSharedImpl}; use kas::event::{config::WindowConfig, ConfigCx, CursorIcon, EventState}; use kas::geom::{Coord, Rect, Size}; use kas::layout::SolveCache; @@ -76,19 +76,11 @@ impl> Window { ) -> super::Result { let time = Instant::now(); - // Wayland only supports windows constructed via logical size - let use_logical_size = shared.shell.platform.is_wayland(); - - let scale_factor = if use_logical_size { - 1.0 - } else { - shared.scale_factor as f32 - }; - - let mut theme_window = shared.shell.theme.new_window(scale_factor); + // We cannot reliably determine the scale factor before window creation. + // A factor of 1.0 lets us estimate the size requirements (logical). + let mut theme_window = shared.shell.theme.new_window(1.0); let dpem = theme_window.size().dpem(); - - self.ev_state.update_config(scale_factor, dpem); + self.ev_state.update_config(1.0, dpem); self.ev_state.full_configure( theme_window.size(), self.window_id, @@ -101,20 +93,18 @@ impl> Window { let mut solve_cache = SolveCache::find_constraints(node, sizer); // Opening a zero-size window causes a crash, so force at least 1x1: - let ideal = solve_cache.ideal(true).max(Size(1, 1)); - let ideal = match use_logical_size { - false => ideal.as_physical(), - true => ideal.as_logical(), - }; + let min_size = Size(1, 1); + let max_size = Size::splat(shared.shell.draw.draw.max_texture_dimension_2d().cast()); + + let ideal = solve_cache + .ideal(true) + .clamp(min_size, max_size) + .as_logical(); let mut builder = WindowBuilder::new().with_inner_size(ideal); let (restrict_min, restrict_max) = self.widget.restrictions(); if restrict_min { - let min = solve_cache.min(true); - let min = match use_logical_size { - false => min.as_physical(), - true => min.as_logical(), - }; + let min = solve_cache.min(true).as_logical(); builder = builder.with_min_inner_size(min); } if restrict_max { @@ -125,10 +115,41 @@ impl> Window { .with_window_icon(self.widget.icon()) .with_decorations(self.widget.decorations() == kas::Decorations::Server) .with_transparent(self.widget.transparent()) + .with_visible(false) .build(elwt)?; + // Now that we have a scale factor, we may need to resize: let scale_factor = window.scale_factor(); - shared.scale_factor = scale_factor; + let apply_size; + if scale_factor != 1.0 { + let sf32 = scale_factor as f32; + shared.shell.theme.update_window(&mut theme_window, sf32); + let dpem = theme_window.size().dpem(); + self.ev_state.update_config(sf32, dpem); + let node = self.widget.as_node(&shared.data); + let sizer = SizeCx::new(theme_window.size()); + solve_cache = SolveCache::find_constraints(node, sizer); + + // NOTE: we would use .as_physical(), but we need to ensure rounding + // doesn't result in anything exceeding max_size which can happen + // otherwise (default rounding mode is to nearest, away from zero). + let ideal = solve_cache.ideal(true).max(min_size); + let ub = (f64::conv(max_size.0) / scale_factor).floor(); + let w = ub.min(f64::conv(ideal.0) / scale_factor); + let h = ub.min(f64::conv(ideal.1) / scale_factor); + let ideal = winit::dpi::LogicalSize::new(w, h); + + if let Some(size) = window.request_inner_size(ideal) { + debug_assert_eq!(size, window.inner_size()); + apply_size = true; + } else { + // We will receive WindowEvent::Resized + apply_size = false; + } + } else { + apply_size = true; + } + let size: Size = window.inner_size().cast(); log::info!( "new: constructed with physical size {:?}, scale factor {}", @@ -136,29 +157,21 @@ impl> Window { scale_factor ); - // Now that we have a scale factor, we may need to resize: - if use_logical_size && scale_factor != 1.0 { - let scale_factor = scale_factor as f32; - shared - .shell - .theme - .update_window(&mut theme_window, scale_factor); - let dpem = theme_window.size().dpem(); - self.ev_state.update_config(scale_factor, dpem); - solve_cache.invalidate_rule_cache(); - } - #[cfg(all(wayland_platform, feature = "clipboard"))] - use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle, WaylandDisplayHandle}; + use raw_window_handle::{HasDisplayHandle, RawDisplayHandle, WaylandDisplayHandle}; + // TODO: this shouldn't unwrap #[cfg(all(wayland_platform, feature = "clipboard"))] - let wayland_clipboard = match window.raw_display_handle() { + let wayland_clipboard = match window.display_handle().unwrap().as_raw() { RawDisplayHandle::Wayland(WaylandDisplayHandle { display, .. }) => { - Some(unsafe { smithay_clipboard::Clipboard::new(display) }) + Some(unsafe { smithay_clipboard::Clipboard::new(display.as_ptr()) }) } _ => None, }; - let surface = S::new(&mut shared.shell.draw.draw, size, &window)?; + let mut surface = S::new(&mut shared.shell.draw.draw, &window)?; + if apply_size { + surface.do_resize(&mut shared.shell.draw.draw, size); + } let winit_id = window.id(); @@ -176,7 +189,9 @@ impl> Window { queued_frame_time: Some(time), }); - self.apply_size(shared, true); + if apply_size { + self.apply_size(shared, true); + } log::trace!(target: "kas_perf::wgpu::window", "resume: {}µs", time.elapsed().as_micros()); Ok(winit_id) @@ -202,7 +217,6 @@ impl> Window { match event { WindowEvent::Moved(_) | WindowEvent::Destroyed => false, WindowEvent::Resized(size) => { - // TODO: maybe enqueue to allow skipping of obsolete resizes if window .surface .do_resize(&mut shared.shell.draw.draw, size.cast()) @@ -213,7 +227,6 @@ impl> Window { } WindowEvent::ScaleFactorChanged { scale_factor, .. } => { // Note: API allows us to set new window size here. - shared.scale_factor = scale_factor; let scale_factor = scale_factor as f32; shared .shell @@ -221,6 +234,9 @@ impl> Window { .update_window(&mut window.theme_window, scale_factor); let dpem = window.theme_window.size().dpem(); self.ev_state.update_config(scale_factor, dpem); + + // NOTE: we could try resizing here in case the window is too + // small due to non-linear scaling, but it appears unnecessary. window.solve_cache.invalidate_rule_cache(); false } @@ -418,6 +434,7 @@ impl> Window { } self.widget.resize_popups(&mut cx, &shared.data); + // Size restrictions may have changed due to content or size (line wrapping) let (restrict_min, restrict_max) = self.widget.restrictions(); if restrict_min { let min = window.solve_cache.min(true).as_physical(); @@ -428,6 +445,7 @@ impl> Window { window.set_max_inner_size(Some(ideal)); }; + window.set_visible(true); window.request_redraw(); log::trace!( target: "kas_perf::wgpu::window", @@ -477,7 +495,7 @@ impl> Window { } else { shared.shell.theme.clear_color() }; - window + let time3 = window .surface .present(&mut shared.shell.draw.draw, clear_color); @@ -485,10 +503,11 @@ impl> Window { let end = Instant::now(); log::trace!( target: "kas_perf::wgpu::window", - "do_draw: {}µs ({}μs widgets, {}µs text, {}µs render)", + "do_draw: {}μs ({}μs widgets, {}μs text, {}μs render, {}μs present)", (end - start).as_micros(), (time2 - start).as_micros(), text_dur_micros.as_micros(), + (time3 - time2).as_micros(), (end - time2).as_micros() ); diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index a18da17eb..320421c7c 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -285,6 +285,13 @@ impl_scope! { State::Rendering(source) | State::Ready(source, _) => State::Ready(source, pixmap), }; + + let own_size: (u32, u32) = self.core.rect.size.cast(); + if size != own_size { + if let Some(fut) = self.inner.resize(size) { + cx.push_spawn(self.id(), fut); + } + } } } } diff --git a/crates/kas-wgpu/Cargo.toml b/crates/kas-wgpu/Cargo.toml index ab00d0790..4b2329a47 100644 --- a/crates/kas-wgpu/Cargo.toml +++ b/crates/kas-wgpu/Cargo.toml @@ -29,7 +29,6 @@ raster = ["kas-text/raster"] bytemuck = "1.7.0" futures-lite = "1.12" log = "0.4" -wgpu = { version = "0.17.0", features = ["spirv"] } thiserror = "1.0.23" guillotiere = "0.6.0" rustc-hash = "1.0" @@ -43,5 +42,11 @@ path = "../kas-core" [dependencies.kas-text] version = "0.6.0" +[dependencies.wgpu] +version = "0.18.0" +features = ["spirv"] +git = "https://github.com/gfx-rs/wgpu.git" +rev = "1dc5347b141f98392fa012ae3416901faf4249a5" + [build-dependencies] glob = "0.3" diff --git a/crates/kas-wgpu/src/draw/draw_pipe.rs b/crates/kas-wgpu/src/draw/draw_pipe.rs index ee9ea26e4..f373ee4c7 100644 --- a/crates/kas-wgpu/src/draw/draw_pipe.rs +++ b/crates/kas-wgpu/src/draw/draw_pipe.rs @@ -47,7 +47,10 @@ impl DrawPipe { }; log::info!("Using graphics adapter: {}", adapter.get_info().name); - let desc = CB::device_descriptor(); + // Use adapter texture size limits to support the largest window surface possible + let mut desc = CB::device_descriptor(); + desc.limits = desc.limits.using_resolution(adapter.limits()); + let trace_path = options.wgpu_trace_path.as_deref(); let req = adapter.request_device(&desc, trace_path); let (device, queue) = block_on(req).map_err(|e| Error::Graphics(Box::new(e)))?; @@ -258,7 +261,7 @@ impl DrawPipe { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(clear_color), - store: true, + store: wgpu::StoreOp::Store, }, })]; @@ -274,6 +277,8 @@ impl DrawPipe { label: Some("kas-wgpu render pass"), color_attachments: &color_attachments, depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, }); rpass.set_scissor_rect( rect.pos.0.cast(), @@ -328,6 +333,10 @@ impl DrawPipe { impl DrawSharedImpl for DrawPipe { type Draw = DrawWindow; + fn max_texture_dimension_2d(&self) -> u32 { + self.device.limits().max_texture_dimension_2d + } + fn set_raster_config(&mut self, config: &RasterConfig) { self.text.set_raster_config(config); } diff --git a/crates/kas-wgpu/src/surface.rs b/crates/kas-wgpu/src/surface.rs index 4a5fabf7b..554df3ad3 100644 --- a/crates/kas-wgpu/src/surface.rs +++ b/crates/kas-wgpu/src/surface.rs @@ -8,7 +8,7 @@ use crate::draw::{CustomPipe, DrawPipe, DrawWindow}; use kas::cast::Cast; use kas::draw::color::Rgba; -use kas::draw::{DrawIface, WindowCommon}; +use kas::draw::{DrawIface, DrawSharedImpl, WindowCommon}; use kas::geom::Size; use kas::shell::{raw_window_handle as raw, Error, WindowSurface}; use std::time::Instant; @@ -23,21 +23,18 @@ pub struct Surface { impl WindowSurface for Surface { type Shared = DrawPipe; - fn new( - shared: &mut Self::Shared, - size: Size, - window: W, - ) -> Result { - let mut draw = shared.new_window(); - shared.resize(&mut draw, size); - + fn new(shared: &mut Self::Shared, window: W) -> Result + where + W: raw::HasWindowHandle + raw::HasDisplayHandle, + Self: Sized, + { let surface = unsafe { shared.instance.create_surface(&window) } .map_err(|e| Error::Graphics(Box::new(e)))?; let sc_desc = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: crate::draw::RENDER_TEX_FORMAT, - width: size.0.cast(), - height: size.1.cast(), + width: 0, + height: 0, present_mode: wgpu::PresentMode::Fifo, // FIXME: current output is for Opaque or PostMultiplied, depending // on window transparency. But we can't pick what we want since only @@ -46,12 +43,11 @@ impl WindowSurface for Surface { alpha_mode: wgpu::CompositeAlphaMode::Auto, view_formats: vec![], }; - surface.configure(&shared.device, &sc_desc); Ok(Surface { surface, sc_desc, - draw, + draw: shared.new_window(), }) } @@ -63,6 +59,8 @@ impl WindowSurface for Surface { if size == self.size() { return false; } + let size = size.min(Size::splat(shared.max_texture_dimension_2d().cast())); + let time = Instant::now(); shared.resize(&mut self.draw, size); @@ -90,14 +88,15 @@ impl WindowSurface for Surface { &mut self.draw.common } - fn present(&mut self, shared: &mut Self::Shared, clear_color: Rgba) { + /// Return time at which render finishes + fn present(&mut self, shared: &mut Self::Shared, clear_color: Rgba) -> Instant { let frame = match self.surface.get_current_texture() { Ok(frame) => frame, Err(e) => { // This error has not been observed. Can it be fixed by // re-configuring the surface? Does it ever occur anyway? log::error!("WindowSurface::present: failed to get frame texture: {}", e); - return; + return Instant::now(); } }; @@ -112,7 +111,9 @@ impl WindowSurface for Surface { let clear_color = to_wgpu_color(clear_color); shared.render(&mut self.draw, &view, clear_color); + let pre_present = Instant::now(); frame.present(); + pre_present } }