From ef392d0322386b4b15253deb77a657de205c98d7 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 23 Aug 2026 11:19:42 +0000 Subject: [PATCH 1/5] Update kas-text --- Cargo.toml | 2 +- crates/kas-core/Cargo.toml | 2 +- crates/kas-core/src/config/config.rs | 1 - crates/kas-core/src/text/raster.rs | 19 ++++++++++++------- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f4e1aa247..637ff5e92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -175,4 +175,4 @@ resolver = "2" [patch.crates-io.kas-text] git = "https://github.com/kas-gui/kas-text.git" -rev = "9554604bbaa40af58114126496a2c20e1553c468" +rev = "d7a40e847b3551eecd33d468b76231cece23631b" diff --git a/crates/kas-core/Cargo.toml b/crates/kas-core/Cargo.toml index c4370b223..cf36d3da7 100644 --- a/crates/kas-core/Cargo.toml +++ b/crates/kas-core/Cargo.toml @@ -114,7 +114,7 @@ rustc-hash = "2.0" ab_glyph = { version = "0.2.10", optional = true } swash = { version = "0.2.4", features = ["scale"] } linearize = { version = "0.1.5", features = ["derive"] } -kas-text = "0.9.0" +kas-text = { version = "0.9.0", features = ["swash"] } easy-cast = "0.7.0" pulldown-cmark = { version = "0.13.0", optional = true } diff --git a/crates/kas-core/src/config/config.rs b/crates/kas-core/src/config/config.rs index 57998525f..0cc54098b 100644 --- a/crates/kas-core/src/config/config.rs +++ b/crates/kas-core/src/config/config.rs @@ -10,7 +10,6 @@ use super::{FontConfig, FontConfigMsg, ThemeConfig, ThemeConfigMsg}; use crate::ConfigAction; use crate::config::Shortcuts; use crate::theme::TextClass; -use core::f32; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use std::cell::{Ref, RefCell}; diff --git a/crates/kas-core/src/text/raster.rs b/crates/kas-core/src/text/raster.rs index 3254f24be..bb39cf201 100644 --- a/crates/kas-core/src/text/raster.rs +++ b/crates/kas-core/src/text/raster.rs @@ -103,7 +103,7 @@ impl std::fmt::Debug for SpriteDescriptor { let y_steps = ((self.0 & 0xF000_0000_0000_0000) >> 60) as u8; f.debug_struct("SpriteDescriptor") .field("face", &self.face()) - .field("glyph", &self.glyph()) + .field("glyph", &self.glyph_u16()) .field("dpem_steps", &dpem_steps) .field("offset_steps", &(x_steps, y_steps)) .finish() @@ -125,7 +125,12 @@ impl SpriteDescriptor { /// Construct pub fn new(config: &Config, face: FaceId, glyph: Glyph, dpem: f32) -> Self { let face: u16 = face.get().cast(); - let glyph_id: u16 = glyph.id.0; + // Input GlyphId is 32-bit. We can only store 16 bits here. + let glyph_id: u32 = if glyph.id.to_u32() <= 0xFFFF { + glyph.id.to_u32() + } else { + GlyphId::NOTDEF.to_u32() + }; let steps = Self::sub_pixel_x_steps(config, dpem); let mult = f32::conv(steps); @@ -149,9 +154,9 @@ impl SpriteDescriptor { FaceId::from((self.0 & 0x0000_0000_0000_FFFF) as u32) } - /// Get `GlyphId` descriptor - pub fn glyph(self) -> GlyphId { - GlyphId(((self.0 & 0x0000_0000_FFFF_0000) >> 16).cast()) + /// Get glyph index (16-bit) + pub fn glyph_u16(self) -> u16 { + ((self.0 & 0x0000_0000_FFFF_0000) >> 16).cast() } /// Get scale (pixels per Em) @@ -347,7 +352,7 @@ impl State { use swash::scale::{Render, Source, StrikeWith, image::Content}; use swash::zeno::{Angle, Transform}; - let face = fonts::library().get_face_store(face_id); + let face = fonts::library().get_face(face_id); let font = face.swash(); let synthesis = face.synthesis(); @@ -395,7 +400,7 @@ impl State { .offset(desc.fractional_position(&self.config).into()) .transform(transform) .embolden(embolden) - .render(&mut scaler, desc.glyph().0) + .render(&mut scaler, desc.glyph_u16()) else { log::warn!("raster_glyphs failed: unable to construct renderer"); self.glyphs.insert(desc, Sprite::default()); From 6f303127f69fd700aeb9698bdc66ab8ad037b861 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 25 Aug 2026 11:19:19 +0000 Subject: [PATCH 2/5] Update resvg, usvg --- crates/kas-image/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/kas-image/Cargo.toml b/crates/kas-image/Cargo.toml index eb7f2d6d0..63039e6e0 100644 --- a/crates/kas-image/Cargo.toml +++ b/crates/kas-image/Cargo.toml @@ -40,8 +40,8 @@ webp = ["dep:image", "image/webp"] [dependencies] log = "0.4" tiny-skia = { version = "0.12.0" } -resvg = { version = "0.47.0", optional = true } -usvg = { version = "0.47.0", optional = true } +resvg = { version = "0.48.1", optional = true } +usvg = { version = "0.48.1", optional = true } once_cell = "1.17.0" thiserror = "2.0.3" image = { version = "0.25.1", default-features = false, optional = true } From 4a076658c13b3c8c58555f767c9667a26ed12813 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 25 Aug 2026 15:08:40 +0000 Subject: [PATCH 3/5] Fix validation warning: only load subpixel shader when required --- crates/kas-wgpu/src/draw/draw_pipe.rs | 1 + crates/kas-wgpu/src/draw/images.rs | 7 ++----- crates/kas-wgpu/src/draw/shaders.rs | 7 +++++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/kas-wgpu/src/draw/draw_pipe.rs b/crates/kas-wgpu/src/draw/draw_pipe.rs index edea2f828..6cb769e49 100644 --- a/crates/kas-wgpu/src/draw/draw_pipe.rs +++ b/crates/kas-wgpu/src/draw/draw_pipe.rs @@ -46,6 +46,7 @@ impl DrawPipe { .features() .contains(wgpu::Features::DUAL_SOURCE_BLENDING) { + log::info!("Enabling feature DUAL_SOURCE_BLENDING for sub-pixel font rendering"); desc.required_features |= wgpu::Features::DUAL_SOURCE_BLENDING; } desc.required_limits = desc.required_limits.using_resolution(adapter.limits()); diff --git a/crates/kas-wgpu/src/draw/images.rs b/crates/kas-wgpu/src/draw/images.rs index 2c3c04040..a5a9bcfc4 100644 --- a/crates/kas-wgpu/src/draw/images.rs +++ b/crates/kas-wgpu/src/draw/images.rs @@ -171,10 +171,7 @@ impl Images { ); let mut atlas_rgba_mask = None; - if device - .features() - .contains(wgpu::Features::DUAL_SOURCE_BLENDING) - { + if let Some(frag_subpixel) = shaders.frag_subpixel.as_ref() { atlas_rgba_mask = Some(atlases::Pipeline::new( device, Some("text subpixel mask pipe"), @@ -198,7 +195,7 @@ impl Images { })], }, wgpu::FragmentState { - module: &shaders.frag_subpixel, + module: frag_subpixel, entry_point: Some("main"), compilation_options: Default::default(), targets: &[Some(wgpu::ColorTargetState { diff --git a/crates/kas-wgpu/src/draw/shaders.rs b/crates/kas-wgpu/src/draw/shaders.rs index 71478de08..d236938c1 100644 --- a/crates/kas-wgpu/src/draw/shaders.rs +++ b/crates/kas-wgpu/src/draw/shaders.rs @@ -21,7 +21,7 @@ pub struct ShaderManager { pub frag_shaded_square: ShaderModule, pub frag_image: ShaderModule, pub frag_glyph: ShaderModule, - pub frag_subpixel: ShaderModule, + pub frag_subpixel: Option, } macro_rules! create { @@ -44,7 +44,10 @@ impl ShaderManager { frag_shaded_square: create!(device, "shaders/shaded_square.frag.spv"), frag_image: create!(device, "shaders/image.frag.spv"), frag_glyph: create!(device, "shaders/glyph.frag.spv"), - frag_subpixel: create!(device, "shaders/subpixel.frag.spv"), + frag_subpixel: device + .features() + .contains(wgpu::Features::DUAL_SOURCE_BLENDING) + .then(|| create!(device, "shaders/subpixel.frag.spv")), } } } From 17b602fd6cfab055dd6dd4c30d4b0f0e82ead418 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 25 Aug 2026 11:26:06 +0000 Subject: [PATCH 4/5] Use raw shaders by default; add feature validate-shaders --- crates/kas-wgpu/Cargo.toml | 4 +++- crates/kas-wgpu/src/draw/draw_pipe.rs | 3 +++ crates/kas-wgpu/src/draw/shaders.rs | 15 +++++++++++++-- examples/mandlebrot/Cargo.toml | 2 +- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/crates/kas-wgpu/Cargo.toml b/crates/kas-wgpu/Cargo.toml index e01cf7919..60fd09da0 100644 --- a/crates/kas-wgpu/Cargo.toml +++ b/crates/kas-wgpu/Cargo.toml @@ -24,6 +24,9 @@ gles = ["wgpu/gles"] dx12 = ["wgpu/dx12"] metal = ["wgpu/metal"] +# Enable SPIRV validation of input shaders +validate-shaders = ["wgpu/spirv"] + [dependencies] bytemuck = "1.7.0" futures-lite = "2.0" @@ -40,7 +43,6 @@ path = "../kas-core" [dependencies.wgpu] version = "30.0.0" default-features = false -features = ["spirv"] [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 6cb769e49..309e5e4ea 100644 --- a/crates/kas-wgpu/src/draw/draw_pipe.rs +++ b/crates/kas-wgpu/src/draw/draw_pipe.rs @@ -41,6 +41,9 @@ impl DrawPipe { // Use adapter texture size limits to support the largest window surface possible let mut desc = CB::device_descriptor(&adapter); + if !cfg!(feature = "validate-shaders") { + desc.required_features |= wgpu::Features::PASSTHROUGH_SHADERS; + } if features.subpixel_rendering && adapter .features() diff --git a/crates/kas-wgpu/src/draw/shaders.rs b/crates/kas-wgpu/src/draw/shaders.rs index d236938c1..8a725cb28 100644 --- a/crates/kas-wgpu/src/draw/shaders.rs +++ b/crates/kas-wgpu/src/draw/shaders.rs @@ -5,7 +5,7 @@ //! Shader management -use wgpu::{ShaderModule, include_spirv}; +use wgpu::ShaderModule; /// Shader manager pub struct ShaderManager { @@ -24,8 +24,19 @@ pub struct ShaderManager { pub frag_subpixel: Option, } +#[cfg(feature = "validate-shaders")] macro_rules! create { - ($device:ident, $path:expr) => {{ $device.create_shader_module(include_spirv!($path)) }}; + ($device:ident, $path:expr) => { + $device.create_shader_module(wgpu::include_spirv!($path)) + }; +} + +#[cfg(not(feature = "validate-shaders"))] +macro_rules! create { + ($device:ident, $path:expr) => { + // SAFETY: this relies on input of valid SPIRV shaders + unsafe { $device.create_shader_module_passthrough(wgpu::include_spirv_raw!($path)) } + }; } impl ShaderManager { diff --git a/examples/mandlebrot/Cargo.toml b/examples/mandlebrot/Cargo.toml index a5012393a..8f0804beb 100644 --- a/examples/mandlebrot/Cargo.toml +++ b/examples/mandlebrot/Cargo.toml @@ -11,7 +11,7 @@ publish = false [dependencies] kas = { version = "0.17.0", features = ["wgpu"], path = "../.." } -kas-wgpu = { version = "0.17.0", path = "../../crates/kas-wgpu" } +kas-wgpu = { version = "0.17.0", path = "../../crates/kas-wgpu", features = ["validate-shaders"] } chrono = "0.4" env_logger = "0.11" log = "0.4" From 83ee2a1d4b5c177b1b5fe43749a8557c85296a43 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 28 Aug 2026 08:19:03 +0000 Subject: [PATCH 5/5] Work around conflict with unstable float_conversions feature --- examples/mandlebrot/mandlebrot.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mandlebrot/mandlebrot.rs b/examples/mandlebrot/mandlebrot.rs index 7d374973d..b6c0dc4ec 100644 --- a/examples/mandlebrot/mandlebrot.rs +++ b/examples/mandlebrot/mandlebrot.rs @@ -390,7 +390,7 @@ mod Mandlebrot { } }, Event::Scroll(delta) => match delta.as_factor_or_offset(cx) { - Ok(factor) => self.transform *= Linear::scale(2f64.powf(factor.cast())), + Ok(factor) => self.transform *= Linear::scale(2f64.powf(Cast::cast(factor))), Err(offset) => { let offset: DVec2 = offset.cast(); self.transform -= self.transform.alpha() * self.view_alpha * offset;