Support immediately resizing without refreshing

On some platforms, notably Wayland, the window immediately responds to
resize requests. This means that if the window resizes itself during the
prepare operation, the current graphics context is no longer the correct
size for the window. Before this change, Kludgine was working around
this by noticing the discrepency and forcing an extra refresh. This
workaround is still in place in Kludgine, but now Cushy supports
restarting redrawing upon resize.
This commit is contained in:
Jonathan Johnson 2024-09-16 08:22:30 -07:00
parent b4058fc55b
commit e7ccbac18c
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
3 changed files with 85 additions and 54 deletions

6
Cargo.lock generated
View file

@ -124,8 +124,7 @@ checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
[[package]] [[package]]
name = "appit" name = "appit"
version = "0.4.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/khonsulabs/appit#c985e024fc090e5764ff41b06d9dcc76ed5aedef"
checksum = "75ed795976c3816aeceb52f1971d3e27f5403d58b16531edea7fe97f63e75f08"
dependencies = [ dependencies = [
"winit", "winit",
] ]
@ -1312,8 +1311,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]] [[package]]
name = "kludgine" name = "kludgine"
version = "0.11.0" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/khonsulabs/kludgine#eccacdaffe4a60d1887a201a68656f0058a12088"
checksum = "32ee58ce44762ccebb879d0df90c736336f6230e633702c7a24643dff1fca25c"
dependencies = [ dependencies = [
"ahash", "ahash",
"alot", "alot",

View file

@ -23,7 +23,9 @@ tokio-multi-thread = ["tokio", "tokio/rt-multi-thread"]
serde = ["dep:serde", "figures/serde"] serde = ["dep:serde", "figures/serde"]
[dependencies] [dependencies]
kludgine = { version = "0.11.0", features = ["app"] } kludgine = { git = "https://github.com/khonsulabs/kludgine", features = [
"app",
] }
figures = { version = "0.4.0" } figures = { version = "0.4.0" }
alot = "0.3" alot = "0.3"
interner = "0.2.1" interner = "0.2.1"

View file

@ -32,7 +32,7 @@ use kludgine::app::winit::keyboard::{
Key, KeyLocation, ModifiersState, NamedKey, NativeKeyCode, PhysicalKey, SmolStr, Key, KeyLocation, ModifiersState, NamedKey, NativeKeyCode, PhysicalKey, SmolStr,
}; };
use kludgine::app::winit::window::{self, Cursor, Fullscreen, Icon, WindowButtons, WindowLevel}; use kludgine::app::winit::window::{self, Cursor, Fullscreen, Icon, WindowButtons, WindowLevel};
use kludgine::app::{winit, WindowAttributes, WindowBehavior as _}; use kludgine::app::{winit, Resized, WindowAttributes, WindowBehavior as _};
use kludgine::cosmic_text::{fontdb, Family, FamilyOwned}; use kludgine::cosmic_text::{fontdb, Family, FamilyOwned};
use kludgine::drawing::Drawing; use kludgine::drawing::Drawing;
use kludgine::shapes::Shape; use kludgine::shapes::Shape;
@ -1771,7 +1771,9 @@ where
}; };
this.synchronize_platform_window(&mut window); this.synchronize_platform_window(&mut window);
this.prepare(window, graphics);
// Perform an initial layout.
let _result = this.prepare(window, graphics);
this this
} }
@ -1795,7 +1797,11 @@ where
.new_frame(self.redraw_status.invalidations().drain()); .new_frame(self.redraw_status.invalidations().drain());
} }
fn prepare<W>(&mut self, mut window: W, graphics: &mut kludgine::Graphics<'_>) fn prepare<W>(
&mut self,
mut window: W,
graphics: &mut kludgine::Graphics<'_>,
) -> Result<(), Resized>
where where
W: PlatformWindowImplementation, W: PlatformWindowImplementation,
{ {
@ -1884,6 +1890,8 @@ where
if let Some(new_size) = new_size { if let Some(new_size) = new_size {
self.inner_size.set_and_read(new_size); self.inner_size.set_and_read(new_size);
self.outer_size.set(layout_context.window().outer_size()); self.outer_size.set(layout_context.window().outer_size());
self.root.invalidate();
return Err(Resized);
} }
self.root.set_layout(Rect::from(render_size.into_signed())); self.root.set_layout(Rect::from(render_size.into_signed()));
@ -1904,6 +1912,7 @@ where
} else { } else {
layout_context.redraw(); layout_context.redraw();
} }
Ok(())
} }
fn close_requested<W>(&mut self, window: W, kludgine: &mut Kludgine) -> bool fn close_requested<W>(&mut self, window: W, kludgine: &mut Kludgine) -> bool
@ -2487,8 +2496,8 @@ where
&mut self, &mut self,
window: kludgine::app::Window<'_, WindowCommand>, window: kludgine::app::Window<'_, WindowCommand>,
graphics: &mut kludgine::Graphics<'_>, graphics: &mut kludgine::Graphics<'_>,
) { ) -> Result<(), Resized> {
self.prepare(window, graphics); self.prepare(window, graphics)
} }
fn present_mode(&self) -> wgpu::PresentMode { fn present_mode(&self) -> wgpu::PresentMode {
@ -3654,14 +3663,25 @@ pub struct CushyWindow {
impl CushyWindow { impl CushyWindow {
/// Prepares all necessary resources and operations necessary to render the /// Prepares all necessary resources and operations necessary to render the
/// next frame. /// next frame.
pub fn prepare<W>(&mut self, window: W, device: &wgpu::Device, queue: &wgpu::Queue) ///
/// # Errors
///
/// If during the preparation of rendering, the window is resized,
/// `Err(Resized)` is returned and Cushy will immediately resize the
/// graphics context and begin rendering again.
pub fn prepare<W>(
&mut self,
window: W,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> Result<(), Resized>
where where
W: PlatformWindowImplementation, W: PlatformWindowImplementation,
{ {
self.window.prepare( self.window.prepare(
window, window,
&mut kludgine::Graphics::new(&mut self.kludgine, device, queue), &mut kludgine::Graphics::new(&mut self.kludgine, device, queue),
); )
} }
/// Renders this window in a wgpu render pass created from `pass`. /// Renders this window in a wgpu render pass created from `pass`.
@ -3884,7 +3904,13 @@ pub struct VirtualWindow {
impl VirtualWindow { impl VirtualWindow {
/// Prepares all necessary resources and operations necessary to render the /// Prepares all necessary resources and operations necessary to render the
/// next frame. /// next frame.
pub fn prepare(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) { ///
/// # Errors
///
/// If during the preparation of rendering, the window is resized,
/// `Err(Resized)` is returned and Cushy will immediately resize the
/// graphics context and begin rendering again.
pub fn prepare(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) -> Result<(), Resized> {
let now = Instant::now(); let now = Instant::now();
self.state.elapsed = self self.state.elapsed = self
.last_rendered_at .last_rendered_at
@ -3892,7 +3918,7 @@ impl VirtualWindow {
.unwrap_or_default(); .unwrap_or_default();
self.last_rendered_at = Some(now); self.last_rendered_at = Some(now);
self.state.dynamic.redraw_target.set(RedrawTarget::Never); self.state.dynamic.redraw_target.set(RedrawTarget::Never);
self.cushy.prepare(&mut self.state, device, queue); self.cushy.prepare(&mut self.state, device, queue)
} }
/// Renders this window in a wgpu render pass created from `pass`. /// Renders this window in a wgpu render pass created from `pass`.
@ -4466,50 +4492,55 @@ where
} }
fn redraw(&mut self) { fn redraw(&mut self) {
let mut render_size = self.window.size().ceil(); loop {
if self.window.state.size != render_size { let mut render_size = self.window.size().ceil();
let current_scale = self.window.dpi_scale(); if self.window.state.size != render_size {
self.window let current_scale = self.window.dpi_scale();
.resize(self.window.state.size, current_scale, &self.queue); self.window
render_size = self.window.state.size; .resize(self.window.state.size, current_scale, &self.queue);
} render_size = self.window.state.size;
let bytes_per_row = copy_buffer_aligned_bytes_per_row(render_size.width.get() * 4); }
let size = u64::from(bytes_per_row) * u64::from(render_size.height.get()); let bytes_per_row = copy_buffer_aligned_bytes_per_row(render_size.width.get() * 4);
self.recreate_buffers_if_needed(render_size, size, bytes_per_row); let size = u64::from(bytes_per_row) * u64::from(render_size.height.get());
self.recreate_buffers_if_needed(render_size, size, bytes_per_row);
let capture = self.capture.as_ref().assert("always initialized above"); let capture = self.capture.as_ref().assert("always initialized above");
if self.cursor_visible { if self.cursor_visible {
let mut gfx = self.window.graphics(&self.device, &self.queue); let mut gfx = self.window.graphics(&self.device, &self.queue);
let mut frame = self.cursor_graphic.new_frame(&mut gfx); let mut frame = self.cursor_graphic.new_frame(&mut gfx);
frame.draw_shape( frame.draw_shape(
Shape::filled_circle(Px::new(4), Color::WHITE, Origin::Center) Shape::filled_circle(Px::new(4), Color::WHITE, Origin::Center)
.translate_by(self.cursor.get()), .translate_by(self.cursor.get()),
);
drop(frame);
}
if let Err(Resized) = self.window.prepare(&self.device, &self.queue) {
continue;
}
self.window.render_with(
&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: capture.multisample.view(),
resolve_target: Some(capture.texture.view()),
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(Color::CLEAR_BLACK.into()),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
&self.device,
&self.queue,
self.cursor_visible.then_some(&self.cursor_graphic),
); );
drop(frame); return;
} }
self.window.prepare(&self.device, &self.queue);
self.window.render_with(
&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: capture.multisample.view(),
resolve_target: Some(capture.texture.view()),
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(Color::CLEAR_BLACK.into()),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
&self.device,
&self.queue,
self.cursor_visible.then_some(&self.cursor_graphic),
);
} }
/// Redraws the contents. /// Redraws the contents.