From 8cbd2f3323b016e7a0bcf70d2f7009336c613fc7 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Tue, 17 Feb 2026 15:25:23 -0800 Subject: [PATCH 1/5] [add] anisotropy clamp to the sampler builder. --- crates/lambda-rs-platform/src/wgpu/texture.rs | 112 +++++++++++++++++- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/crates/lambda-rs-platform/src/wgpu/texture.rs b/crates/lambda-rs-platform/src/wgpu/texture.rs index cc9c37b9..1d0c6cb1 100644 --- a/crates/lambda-rs-platform/src/wgpu/texture.rs +++ b/crates/lambda-rs-platform/src/wgpu/texture.rs @@ -528,6 +528,7 @@ pub struct SamplerBuilder { address_w: AddressMode, lod_min: f32, lod_max: f32, + anisotropy_clamp: u16, } impl Default for SamplerBuilder { @@ -549,6 +550,7 @@ impl SamplerBuilder { address_w: AddressMode::ClampToEdge, lod_min: 0.0, lod_max: 32.0, + anisotropy_clamp: 1, }; } @@ -621,7 +623,65 @@ impl SamplerBuilder { return self; } - fn to_descriptor(&self) -> wgpu::SamplerDescriptor<'_> { + /// Set the maximum anisotropic filtering level. + /// + /// Valid values are `1` (disabled) through `16`. Values outside this range + /// are clamped. Higher values improve texture quality at oblique viewing + /// angles but increase GPU cost. + /// + /// Common values: + /// - `1`: Disabled (default) + /// - `4`: Good balance of quality and performance + /// - `8`: High quality + /// - `16`: Maximum quality + /// + /// Note: Anisotropic filtering is most effective with linear filtering and + /// mipmapped textures. wgpu also requires all filter modes to be linear when + /// anisotropy is enabled; otherwise anisotropy is disabled. + /// + /// ```no_run + /// # use lambda_platform::wgpu::texture::{FilterMode, SamplerBuilder}; + /// # fn demo(gpu: &lambda_platform::wgpu::gpu::Gpu) { + /// // High-quality sampler for floor/wall textures viewed at angles + /// let aniso_sampler = SamplerBuilder::new() + /// .linear_clamp() + /// .with_mip_filter(FilterMode::Linear) + /// .with_anisotropy_clamp(8) + /// .build(gpu); + /// + /// // Default sampler (no anisotropy) for UI textures + /// let ui_sampler = SamplerBuilder::new().linear_clamp().build(gpu); + /// # let _ = (aniso_sampler, ui_sampler); + /// # } + /// ``` + pub fn with_anisotropy_clamp(mut self, clamp: u16) -> Self { + self.anisotropy_clamp = clamp.clamp(1, 16); + return self; + } + + fn to_descriptor( + &self, + max_supported_anisotropy: u16, + ) -> wgpu::SamplerDescriptor<'_> { + let max_supported_anisotropy = max_supported_anisotropy.clamp(1, 16); + let mut anisotropy_clamp = self + .anisotropy_clamp + .clamp(1, 16) + .min(max_supported_anisotropy); + if anisotropy_clamp > 1 + && !matches!( + (self.min_filter, self.mag_filter, self.mipmap_filter), + (FilterMode::Linear, FilterMode::Linear, FilterMode::Linear) + ) + { + logging::warn!( + "Sampler anisotropy requested ({}), but all filters must be \ +linear; anisotropy disabled.", + anisotropy_clamp + ); + anisotropy_clamp = 1; + } + return wgpu::SamplerDescriptor { label: self.label.as_deref(), address_mode_u: self.address_u.to_wgpu(), @@ -632,13 +692,23 @@ impl SamplerBuilder { mipmap_filter: self.mipmap_filter.to_wgpu_mipmap(), lod_min_clamp: self.lod_min, lod_max_clamp: self.lod_max, + anisotropy_clamp, ..Default::default() }; } /// Create the sampler on the provided device. pub fn build(self, gpu: &Gpu) -> Sampler { - let desc = self.to_descriptor(); + let downlevel = gpu.adapter().get_downlevel_capabilities(); + let max_supported_anisotropy = if downlevel + .flags + .contains(wgpu::DownlevelFlags::ANISOTROPIC_FILTERING) + { + 16 + } else { + 1 + }; + let desc = self.to_descriptor(max_supported_anisotropy); let raw = gpu.device().create_sampler(&desc); return Sampler { raw, @@ -1051,7 +1121,7 @@ mod tests { #[test] fn sampler_builder_defaults_map() { let b = SamplerBuilder::new(); - let d = b.to_descriptor(); + let d = b.to_descriptor(16); assert_eq!(d.address_mode_u, wgpu::AddressMode::ClampToEdge); assert_eq!(d.address_mode_v, wgpu::AddressMode::ClampToEdge); assert_eq!(d.address_mode_w, wgpu::AddressMode::ClampToEdge); @@ -1060,6 +1130,7 @@ mod tests { assert_eq!(d.mipmap_filter, wgpu::MipmapFilterMode::Nearest); assert_eq!(d.lod_min_clamp, 0.0); assert_eq!(d.lod_max_clamp, 32.0); + assert_eq!(d.anisotropy_clamp, 1); } #[test] @@ -1067,7 +1138,7 @@ mod tests { let b = SamplerBuilder::new() .linear_clamp() .with_mip_filter(FilterMode::Linear); - let d = b.to_descriptor(); + let d = b.to_descriptor(16); assert_eq!(d.address_mode_u, wgpu::AddressMode::ClampToEdge); assert_eq!(d.address_mode_v, wgpu::AddressMode::ClampToEdge); assert_eq!(d.address_mode_w, wgpu::AddressMode::ClampToEdge); @@ -1075,4 +1146,37 @@ mod tests { assert_eq!(d.min_filter, wgpu::FilterMode::Linear); assert_eq!(d.mipmap_filter, wgpu::MipmapFilterMode::Linear); } + + #[test] + fn sampler_builder_anisotropy_is_clamped_and_passed_through() { + let b = SamplerBuilder::new() + .linear_clamp() + .with_mip_filter(FilterMode::Linear) + .with_anisotropy_clamp(8); + assert_eq!(b.to_descriptor(16).anisotropy_clamp, 8); + assert_eq!(b.to_descriptor(4).anisotropy_clamp, 4); + assert_eq!(b.to_descriptor(1).anisotropy_clamp, 1); + } + + #[test] + fn sampler_builder_anisotropy_clamps_to_valid_range() { + assert_eq!( + SamplerBuilder::new() + .linear_clamp() + .with_mip_filter(FilterMode::Linear) + .with_anisotropy_clamp(0) + .to_descriptor(16) + .anisotropy_clamp, + 1 + ); + assert_eq!( + SamplerBuilder::new() + .linear_clamp() + .with_mip_filter(FilterMode::Linear) + .with_anisotropy_clamp(100) + .to_descriptor(16) + .anisotropy_clamp, + 16 + ); + } } From aec8514c102eedb56f4bbd44e1664018a2847710 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Tue, 17 Feb 2026 18:01:02 -0800 Subject: [PATCH 2/5] [add] anisotropic clamping to public api --- crates/lambda-rs/src/render/texture.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/lambda-rs/src/render/texture.rs b/crates/lambda-rs/src/render/texture.rs index c68d925d..bfd1fcc0 100644 --- a/crates/lambda-rs/src/render/texture.rs +++ b/crates/lambda-rs/src/render/texture.rs @@ -627,6 +627,19 @@ impl SamplerBuilder { return self; } + /// Set the maximum anisotropic filtering level. + /// + /// Valid values are `1` (disabled) through `16`. Values outside this range + /// are clamped. Higher values improve texture quality at oblique viewing + /// angles but increase GPU cost. + /// + /// Note: Anisotropic filtering is most effective with linear filtering and + /// mipmapped textures. + pub fn with_anisotropy_clamp(mut self, clamp: u16) -> Self { + self.inner = self.inner.with_anisotropy_clamp(clamp); + return self; + } + /// Attach a debug label. pub fn with_label(mut self, label: &str) -> Self { self.inner = self.inner.with_label(label); From 4c0b453123a5521da1ad4d28bdf704a704b95302 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Tue, 17 Feb 2026 19:20:02 -0800 Subject: [PATCH 3/5] [fix] redunant clamping --- crates/lambda-rs-platform/src/wgpu/texture.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/lambda-rs-platform/src/wgpu/texture.rs b/crates/lambda-rs-platform/src/wgpu/texture.rs index 1d0c6cb1..7fbaf358 100644 --- a/crates/lambda-rs-platform/src/wgpu/texture.rs +++ b/crates/lambda-rs-platform/src/wgpu/texture.rs @@ -664,10 +664,8 @@ impl SamplerBuilder { max_supported_anisotropy: u16, ) -> wgpu::SamplerDescriptor<'_> { let max_supported_anisotropy = max_supported_anisotropy.clamp(1, 16); - let mut anisotropy_clamp = self - .anisotropy_clamp - .clamp(1, 16) - .min(max_supported_anisotropy); + let mut anisotropy_clamp = + self.anisotropy_clamp.min(max_supported_anisotropy); if anisotropy_clamp > 1 && !matches!( (self.min_filter, self.mag_filter, self.mipmap_filter), From 33689dd82216ef9dcf72b37c805341562a5464c6 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Tue, 17 Feb 2026 19:24:45 -0800 Subject: [PATCH 4/5] [add] warning when setting anisotropy to a level higher than what the GPU supports. --- crates/lambda-rs-platform/src/wgpu/texture.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/lambda-rs-platform/src/wgpu/texture.rs b/crates/lambda-rs-platform/src/wgpu/texture.rs index 7fbaf358..d4048d33 100644 --- a/crates/lambda-rs-platform/src/wgpu/texture.rs +++ b/crates/lambda-rs-platform/src/wgpu/texture.rs @@ -697,15 +697,20 @@ linear; anisotropy disabled.", /// Create the sampler on the provided device. pub fn build(self, gpu: &Gpu) -> Sampler { + let requested_anisotropy = self.anisotropy_clamp.clamp(1, 16); let downlevel = gpu.adapter().get_downlevel_capabilities(); - let max_supported_anisotropy = if downlevel + let supports_anisotropy = downlevel .flags - .contains(wgpu::DownlevelFlags::ANISOTROPIC_FILTERING) - { - 16 - } else { - 1 - }; + .contains(wgpu::DownlevelFlags::ANISOTROPIC_FILTERING); + if requested_anisotropy > 1 && !supports_anisotropy { + logging::warn!( + "Sampler anisotropy requested ({}), but adapter does not report \ +anisotropic filtering support; anisotropy disabled.", + requested_anisotropy + ); + } + + let max_supported_anisotropy = if supports_anisotropy { 16 } else { 1 }; let desc = self.to_descriptor(max_supported_anisotropy); let raw = gpu.device().create_sampler(&desc); return Sampler { From 1d65643210bffc84b0ad4f6c7a32c2a4e4a45cd9 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Tue, 17 Feb 2026 19:27:31 -0800 Subject: [PATCH 5/5] [add] test to ensure that anisotropy becomes 1 when requested but filters remain non-linear --- crates/lambda-rs-platform/src/wgpu/texture.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/lambda-rs-platform/src/wgpu/texture.rs b/crates/lambda-rs-platform/src/wgpu/texture.rs index d4048d33..d9975725 100644 --- a/crates/lambda-rs-platform/src/wgpu/texture.rs +++ b/crates/lambda-rs-platform/src/wgpu/texture.rs @@ -1182,4 +1182,26 @@ mod tests { 16 ); } + + #[test] + fn sampler_builder_anisotropy_is_disabled_when_filters_not_all_linear() { + // Default builder uses nearest filters, so anisotropy must be disabled. + assert_eq!( + SamplerBuilder::new() + .with_anisotropy_clamp(8) + .to_descriptor(16) + .anisotropy_clamp, + 1 + ); + + // If mipmap filtering isn't linear, anisotropy must be disabled. + assert_eq!( + SamplerBuilder::new() + .linear() + .with_anisotropy_clamp(8) + .to_descriptor(16) + .anisotropy_clamp, + 1 + ); + } }