Skip to content

Commit 622843f

Browse files
committed
Merge branch 'main' into vmarcella/2d-collisions
2 parents aae44f7 + 52e6a34 commit 622843f

21 files changed

Lines changed: 1127 additions & 239 deletions

File tree

crates/lambda-rs-args/src/lib.rs

Lines changed: 262 additions & 83 deletions
Large diffs are not rendered by default.

crates/lambda-rs-platform/src/wgpu/texture.rs

Lines changed: 133 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

533534
impl 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

Comments
 (0)