@@ -528,6 +528,7 @@ pub struct SamplerBuilder {
528528 address_w : AddressMode ,
529529 lod_min : f32 ,
530530 lod_max : f32 ,
531+ anisotropy_clamp : u16 ,
531532}
532533
533534impl Default for SamplerBuilder {
@@ -549,6 +550,7 @@ impl SamplerBuilder {
549550 address_w : AddressMode :: ClampToEdge ,
550551 lod_min : 0.0 ,
551552 lod_max : 32.0 ,
553+ anisotropy_clamp : 1 ,
552554 } ;
553555 }
554556
@@ -621,7 +623,63 @@ impl SamplerBuilder {
621623 return self ;
622624 }
623625
624- fn to_descriptor ( & self ) -> wgpu:: SamplerDescriptor < ' _ > {
626+ /// Set the maximum anisotropic filtering level.
627+ ///
628+ /// Valid values are `1` (disabled) through `16`. Values outside this range
629+ /// are clamped. Higher values improve texture quality at oblique viewing
630+ /// angles but increase GPU cost.
631+ ///
632+ /// Common values:
633+ /// - `1`: Disabled (default)
634+ /// - `4`: Good balance of quality and performance
635+ /// - `8`: High quality
636+ /// - `16`: Maximum quality
637+ ///
638+ /// Note: Anisotropic filtering is most effective with linear filtering and
639+ /// mipmapped textures. wgpu also requires all filter modes to be linear when
640+ /// anisotropy is enabled; otherwise anisotropy is disabled.
641+ ///
642+ /// ```no_run
643+ /// # use lambda_platform::wgpu::texture::{FilterMode, SamplerBuilder};
644+ /// # fn demo(gpu: &lambda_platform::wgpu::gpu::Gpu) {
645+ /// // High-quality sampler for floor/wall textures viewed at angles
646+ /// let aniso_sampler = SamplerBuilder::new()
647+ /// .linear_clamp()
648+ /// .with_mip_filter(FilterMode::Linear)
649+ /// .with_anisotropy_clamp(8)
650+ /// .build(gpu);
651+ ///
652+ /// // Default sampler (no anisotropy) for UI textures
653+ /// let ui_sampler = SamplerBuilder::new().linear_clamp().build(gpu);
654+ /// # let _ = (aniso_sampler, ui_sampler);
655+ /// # }
656+ /// ```
657+ pub fn with_anisotropy_clamp ( mut self , clamp : u16 ) -> Self {
658+ self . anisotropy_clamp = clamp. clamp ( 1 , 16 ) ;
659+ return self ;
660+ }
661+
662+ fn to_descriptor (
663+ & self ,
664+ max_supported_anisotropy : u16 ,
665+ ) -> wgpu:: SamplerDescriptor < ' _ > {
666+ let max_supported_anisotropy = max_supported_anisotropy. clamp ( 1 , 16 ) ;
667+ let mut anisotropy_clamp =
668+ self . anisotropy_clamp . min ( max_supported_anisotropy) ;
669+ if anisotropy_clamp > 1
670+ && !matches ! (
671+ ( self . min_filter, self . mag_filter, self . mipmap_filter) ,
672+ ( FilterMode :: Linear , FilterMode :: Linear , FilterMode :: Linear )
673+ )
674+ {
675+ logging:: warn!(
676+ "Sampler anisotropy requested ({}), but all filters must be \
677+ linear; anisotropy disabled.",
678+ anisotropy_clamp
679+ ) ;
680+ anisotropy_clamp = 1 ;
681+ }
682+
625683 return wgpu:: SamplerDescriptor {
626684 label : self . label . as_deref ( ) ,
627685 address_mode_u : self . address_u . to_wgpu ( ) ,
@@ -632,13 +690,28 @@ impl SamplerBuilder {
632690 mipmap_filter : self . mipmap_filter . to_wgpu_mipmap ( ) ,
633691 lod_min_clamp : self . lod_min ,
634692 lod_max_clamp : self . lod_max ,
693+ anisotropy_clamp,
635694 ..Default :: default ( )
636695 } ;
637696 }
638697
639698 /// Create the sampler on the provided device.
640699 pub fn build ( self , gpu : & Gpu ) -> Sampler {
641- let desc = self . to_descriptor ( ) ;
700+ let requested_anisotropy = self . anisotropy_clamp . clamp ( 1 , 16 ) ;
701+ let downlevel = gpu. adapter ( ) . get_downlevel_capabilities ( ) ;
702+ let supports_anisotropy = downlevel
703+ . flags
704+ . contains ( wgpu:: DownlevelFlags :: ANISOTROPIC_FILTERING ) ;
705+ if requested_anisotropy > 1 && !supports_anisotropy {
706+ logging:: warn!(
707+ "Sampler anisotropy requested ({}), but adapter does not report \
708+ anisotropic filtering support; anisotropy disabled.",
709+ requested_anisotropy
710+ ) ;
711+ }
712+
713+ let max_supported_anisotropy = if supports_anisotropy { 16 } else { 1 } ;
714+ let desc = self . to_descriptor ( max_supported_anisotropy) ;
642715 let raw = gpu. device ( ) . create_sampler ( & desc) ;
643716 return Sampler {
644717 raw,
@@ -1051,7 +1124,7 @@ mod tests {
10511124 #[ test]
10521125 fn sampler_builder_defaults_map ( ) {
10531126 let b = SamplerBuilder :: new ( ) ;
1054- let d = b. to_descriptor ( ) ;
1127+ let d = b. to_descriptor ( 16 ) ;
10551128 assert_eq ! ( d. address_mode_u, wgpu:: AddressMode :: ClampToEdge ) ;
10561129 assert_eq ! ( d. address_mode_v, wgpu:: AddressMode :: ClampToEdge ) ;
10571130 assert_eq ! ( d. address_mode_w, wgpu:: AddressMode :: ClampToEdge ) ;
@@ -1060,19 +1133,75 @@ mod tests {
10601133 assert_eq ! ( d. mipmap_filter, wgpu:: MipmapFilterMode :: Nearest ) ;
10611134 assert_eq ! ( d. lod_min_clamp, 0.0 ) ;
10621135 assert_eq ! ( d. lod_max_clamp, 32.0 ) ;
1136+ assert_eq ! ( d. anisotropy_clamp, 1 ) ;
10631137 }
10641138
10651139 #[ test]
10661140 fn sampler_builder_linear_clamp_map ( ) {
10671141 let b = SamplerBuilder :: new ( )
10681142 . linear_clamp ( )
10691143 . with_mip_filter ( FilterMode :: Linear ) ;
1070- let d = b. to_descriptor ( ) ;
1144+ let d = b. to_descriptor ( 16 ) ;
10711145 assert_eq ! ( d. address_mode_u, wgpu:: AddressMode :: ClampToEdge ) ;
10721146 assert_eq ! ( d. address_mode_v, wgpu:: AddressMode :: ClampToEdge ) ;
10731147 assert_eq ! ( d. address_mode_w, wgpu:: AddressMode :: ClampToEdge ) ;
10741148 assert_eq ! ( d. mag_filter, wgpu:: FilterMode :: Linear ) ;
10751149 assert_eq ! ( d. min_filter, wgpu:: FilterMode :: Linear ) ;
10761150 assert_eq ! ( d. mipmap_filter, wgpu:: MipmapFilterMode :: Linear ) ;
10771151 }
1152+
1153+ #[ test]
1154+ fn sampler_builder_anisotropy_is_clamped_and_passed_through ( ) {
1155+ let b = SamplerBuilder :: new ( )
1156+ . linear_clamp ( )
1157+ . with_mip_filter ( FilterMode :: Linear )
1158+ . with_anisotropy_clamp ( 8 ) ;
1159+ assert_eq ! ( b. to_descriptor( 16 ) . anisotropy_clamp, 8 ) ;
1160+ assert_eq ! ( b. to_descriptor( 4 ) . anisotropy_clamp, 4 ) ;
1161+ assert_eq ! ( b. to_descriptor( 1 ) . anisotropy_clamp, 1 ) ;
1162+ }
1163+
1164+ #[ test]
1165+ fn sampler_builder_anisotropy_clamps_to_valid_range ( ) {
1166+ assert_eq ! (
1167+ SamplerBuilder :: new( )
1168+ . linear_clamp( )
1169+ . with_mip_filter( FilterMode :: Linear )
1170+ . with_anisotropy_clamp( 0 )
1171+ . to_descriptor( 16 )
1172+ . anisotropy_clamp,
1173+ 1
1174+ ) ;
1175+ assert_eq ! (
1176+ SamplerBuilder :: new( )
1177+ . linear_clamp( )
1178+ . with_mip_filter( FilterMode :: Linear )
1179+ . with_anisotropy_clamp( 100 )
1180+ . to_descriptor( 16 )
1181+ . anisotropy_clamp,
1182+ 16
1183+ ) ;
1184+ }
1185+
1186+ #[ test]
1187+ fn sampler_builder_anisotropy_is_disabled_when_filters_not_all_linear ( ) {
1188+ // Default builder uses nearest filters, so anisotropy must be disabled.
1189+ assert_eq ! (
1190+ SamplerBuilder :: new( )
1191+ . with_anisotropy_clamp( 8 )
1192+ . to_descriptor( 16 )
1193+ . anisotropy_clamp,
1194+ 1
1195+ ) ;
1196+
1197+ // If mipmap filtering isn't linear, anisotropy must be disabled.
1198+ assert_eq ! (
1199+ SamplerBuilder :: new( )
1200+ . linear( )
1201+ . with_anisotropy_clamp( 8 )
1202+ . to_descriptor( 16 )
1203+ . anisotropy_clamp,
1204+ 1
1205+ ) ;
1206+ }
10781207}
0 commit comments