Skip to content

Commit 6139be0

Browse files
committed
Add support for repeating texture wrap modes.
This is a less ambitious version of NiklasEi#216 that should be ready to go. It changes `#[image(sampler = linear)]` and `#[image(sampler = nearest)]` to `#[image(sampler(filter = linear))]` and `#[image(sampler(filter = nearest))]` respectively. The `sampler` list also supports `repeat` and `clamp` modes. So, for example, you can write `#[image(sampler(filter = linear, repeat)]` or `#[image(sampler(repeat))]`. Closes NiklasEi#235.
1 parent 5a54816 commit 6139be0

File tree

5 files changed

+157
-50
lines changed

5 files changed

+157
-50
lines changed

README.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,16 @@ use bevy_asset_loader::asset_collection::AssetCollection;
245245
#[derive(AssetCollection, Resource)]
246246
struct ImageAssets {
247247
#[asset(path = "images/pixel_tree.png")]
248-
#[asset(image(sampler = linear))]
248+
#[asset(image(sampler(filter = linear)))]
249249
tree_linear: Handle<Image>,
250250

251251
#[asset(path = "images/pixel_tree.png")]
252-
#[asset(image(sampler = nearest))]
252+
#[asset(image(sampler(filter = nearest)))]
253253
tree_nearest: Handle<Image>,
254+
255+
#[asset(path = "images/pixel_tree.png")]
256+
#[asset(image(sampler(filter = linear, repeat)))]
257+
tree_linear_repeat: Handle<Image>,
254258
}
255259
```
256260

@@ -260,11 +264,18 @@ The corresponding dynamic asset would be
260264
({
261265
"tree_nearest": Image (
262266
path: "images/tree.png",
263-
sampler: Nearest
267+
filter: Nearest,
268+
wrap: Clamp
264269
),
265270
"tree_linear": Image (
266271
path: "images/tree.png",
267-
sampler: Linear
272+
filter: Linear,
273+
wrap: Clamp
274+
),
275+
"tree_linear_repeat": Image (
276+
path: "images/tree.png",
277+
filter: Linear,
278+
wrap: Repeat
268279
),
269280
})
270281
```

bevy_asset_loader/examples/atlas_from_grid.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ struct MyAssets {
2929
#[asset(texture_atlas_layout(tile_size_x = 96, tile_size_y = 99, columns = 8, rows = 1))]
3030
female_adventurer_layout: Handle<TextureAtlasLayout>,
3131
// you can configure the sampler for the sprite sheet image
32-
#[asset(image(sampler = nearest))]
32+
#[asset(image(sampler(filter = nearest)))]
3333
#[asset(path = "images/female_adventurer_sheet.png")]
3434
female_adventurer: Handle<Image>,
3535
}

bevy_asset_loader/examples/image_asset.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ fn main() {
1818
#[derive(AssetCollection, Resource)]
1919
struct ImageAssets {
2020
#[asset(path = "images/pixel_tree.png")]
21-
#[asset(image(sampler = linear))]
21+
#[asset(image(sampler(filter = linear)))]
2222
tree_linear: Handle<Image>,
2323

2424
#[asset(path = "images/pixel_tree.png")]
25-
#[asset(image(sampler = nearest))]
25+
#[asset(image(sampler(filter = nearest)))]
2626
tree_nearest: Handle<Image>,
2727

2828
#[asset(path = "images/array_texture.png")]

bevy_asset_loader_derive/src/assets.rs

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::{ParseFieldError, TextureAtlasAttribute};
22
use proc_macro2::{Ident, TokenStream};
33
use quote::quote;
4+
use syn::{spanned::Spanned, Lit, LitStr};
45

56
#[derive(PartialEq, Debug)]
67
pub(crate) struct TextureAtlasLayoutAssetField {
@@ -16,12 +17,12 @@ pub(crate) struct TextureAtlasLayoutAssetField {
1617
}
1718

1819
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
19-
pub(crate) enum SamplerType {
20+
pub(crate) enum FilterType {
2021
Linear,
2122
Nearest,
2223
}
2324

24-
impl TryFrom<String> for SamplerType {
25+
impl TryFrom<String> for FilterType {
2526
type Error = &'static str;
2627
fn try_from(value: String) -> Result<Self, Self::Error> {
2728
match value.as_str() {
@@ -32,11 +33,20 @@ impl TryFrom<String> for SamplerType {
3233
}
3334
}
3435

36+
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
37+
pub(crate) enum WrapMode {
38+
#[default]
39+
Clamp,
40+
#[allow(dead_code)]
41+
Repeat,
42+
}
43+
3544
#[derive(PartialEq, Debug)]
3645
pub(crate) struct ImageAssetField {
3746
pub field_ident: Ident,
3847
pub asset_path: String,
39-
pub sampler: Option<SamplerType>,
48+
pub filter: Option<FilterType>,
49+
pub wrap: Option<WrapMode>,
4050
pub array_texture_layers: Option<u32>,
4151
}
4252

@@ -124,18 +134,19 @@ impl AssetField {
124134
let field_ident = image.field_ident.clone();
125135
let asset_path = image.asset_path.clone();
126136
let layers = image.array_texture_layers.unwrap_or_default();
127-
let sampler = match image.sampler {
128-
Some(SamplerType::Linear) | None => quote!(ImageSampler::linear()),
129-
Some(SamplerType::Nearest) => quote!(ImageSampler::nearest()),
137+
let filter = match image.filter {
138+
Some(FilterType::Linear) | None => quote!(ImageFilterMode::Linear),
139+
Some(FilterType::Nearest) => quote!(ImageFilterMode::Nearest),
130140
};
131-
let descriptor = match image.sampler {
132-
Some(SamplerType::Linear) | None => quote!(ImageSamplerDescriptor::linear()),
133-
Some(SamplerType::Nearest) => quote!(ImageSamplerDescriptor::nearest()),
141+
let wrap = match image.wrap {
142+
Some(WrapMode::Clamp) | None => quote!(ImageAddressMode::ClampToEdge),
143+
Some(WrapMode::Repeat) => quote!(ImageAddressMode::Repeat),
134144
};
135-
let is_sampler_set = image.sampler.is_some();
145+
let is_sampler_set = image.filter.is_some() || image.wrap.is_some();
146+
let label = Lit::Str(LitStr::new(&field_ident.to_string(), token_stream.span()));
136147

137148
quote!(#token_stream #field_ident : {
138-
use bevy::render::texture::{ImageSampler, ImageSamplerDescriptor};
149+
use bevy::render::texture::{ImageAddressMode, ImageFilterMode, ImageSampler, ImageSamplerDescriptor};
139150
let mut system_state = ::bevy::ecs::system::SystemState::<(
140151
ResMut<::bevy::prelude::Assets<::bevy::prelude::Image>>,
141152
Res<::bevy::prelude::AssetServer>,
@@ -149,19 +160,30 @@ impl AssetField {
149160
image.reinterpret_stacked_2d_as_array(#layers);
150161
}
151162

163+
let this_descriptor = ImageSamplerDescriptor {
164+
label: Some(#label.to_string()),
165+
address_mode_u: #wrap,
166+
address_mode_v: #wrap,
167+
address_mode_w: #wrap,
168+
mag_filter: #filter,
169+
min_filter: #filter,
170+
mipmap_filter: #filter,
171+
..::std::default::Default::default()
172+
};
173+
152174
if (#is_sampler_set) {
153175
let is_different_sampler = if let ImageSampler::Descriptor(descriptor) = &image.sampler {
154-
!descriptor.as_wgpu().eq(&#descriptor.as_wgpu())
176+
!descriptor.as_wgpu().eq(&this_descriptor.as_wgpu())
155177
} else {
156-
false
178+
true
157179
};
158180

159181
if is_different_sampler {
160182
let mut cloned_image = image.clone();
161-
cloned_image.sampler = #sampler;
183+
cloned_image.sampler = ImageSampler::Descriptor(this_descriptor);
162184
handle = images.add(cloned_image);
163185
} else {
164-
image.sampler = #sampler;
186+
image.sampler = ImageSampler::Default;
165187
}
166188
}
167189

@@ -539,7 +561,8 @@ pub(crate) struct AssetBuilder {
539561
pub padding_y: Option<u32>,
540562
pub offset_x: Option<u32>,
541563
pub offset_y: Option<u32>,
542-
pub sampler: Option<SamplerType>,
564+
pub filter: Option<FilterType>,
565+
pub wrap: Option<WrapMode>,
543566
pub array_texture_layers: Option<u32>,
544567
}
545568

@@ -665,11 +688,12 @@ impl AssetBuilder {
665688
self.is_mapped.into(),
666689
));
667690
}
668-
if self.sampler.is_some() || self.array_texture_layers.is_some() {
691+
if self.filter.is_some() || self.array_texture_layers.is_some() {
669692
return Ok(AssetField::Image(ImageAssetField {
670693
field_ident: self.field_ident.unwrap(),
671694
asset_path: self.asset_path.unwrap(),
672-
sampler: self.sampler,
695+
filter: self.filter,
696+
wrap: self.wrap,
673697
array_texture_layers: self.array_texture_layers,
674698
}));
675699
}
@@ -929,14 +953,16 @@ mod test {
929953
let builder_linear = AssetBuilder {
930954
field_ident: Some(Ident::new("test", Span::call_site())),
931955
asset_path: Some("some/image.png".to_owned()),
932-
sampler: Some(SamplerType::Linear),
956+
filter: Some(FilterType::Linear),
957+
wrap: None,
933958
..Default::default()
934959
};
935960

936961
let builder_nearest = AssetBuilder {
937962
field_ident: Some(Ident::new("test", Span::call_site())),
938963
asset_path: Some("some/image.png".to_owned()),
939-
sampler: Some(SamplerType::Nearest),
964+
filter: Some(FilterType::Nearest),
965+
wrap: None,
940966
..Default::default()
941967
};
942968

@@ -960,7 +986,8 @@ mod test {
960986
AssetField::Image(ImageAssetField {
961987
field_ident: Ident::new("test", Span::call_site()),
962988
asset_path: "some/image.png".to_owned(),
963-
sampler: Some(SamplerType::Linear),
989+
filter: Some(FilterType::Linear),
990+
wrap: None,
964991
array_texture_layers: None
965992
})
966993
);
@@ -969,7 +996,8 @@ mod test {
969996
AssetField::Image(ImageAssetField {
970997
field_ident: Ident::new("test", Span::call_site()),
971998
asset_path: "some/image.png".to_owned(),
972-
sampler: Some(SamplerType::Nearest),
999+
filter: Some(FilterType::Nearest),
1000+
wrap: None,
9731001
array_texture_layers: None
9741002
})
9751003
);
@@ -978,7 +1006,8 @@ mod test {
9781006
AssetField::Image(ImageAssetField {
9791007
field_ident: Ident::new("test", Span::call_site()),
9801008
asset_path: "some/image.png".to_owned(),
981-
sampler: None,
1009+
filter: None,
1010+
wrap: None,
9821011
array_texture_layers: Some(42)
9831012
})
9841013
);

bevy_asset_loader_derive/src/lib.rs

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,17 @@ impl ImageAttribute {
6666
pub const LAYERS: &'static str = "array_texture_layers";
6767
}
6868

69+
#[allow(dead_code)]
70+
pub(crate) struct SamplerAttribute;
71+
impl SamplerAttribute {
72+
#[allow(dead_code)]
73+
pub const FILTER: &'static str = "filter";
74+
#[allow(dead_code)]
75+
pub const CLAMP: &'static str = "clamp";
76+
#[allow(dead_code)]
77+
pub const REPEAT: &'static str = "repeat";
78+
}
79+
6980
pub(crate) const COLLECTION_ATTRIBUTE: &str = "collection";
7081
pub(crate) const PATHS_ATTRIBUTE: &str = "paths";
7182
pub(crate) const TYPED_ATTRIBUTE: &str = "typed";
@@ -455,30 +466,82 @@ fn parse_field(field: &Field) -> Result<AssetField, Vec<ParseFieldError>> {
455466
.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated);
456467
for attribute in image_meta_list.unwrap() {
457468
match attribute {
458-
Meta::NameValue(named_value) => {
459-
let path = named_value.path.get_ident().unwrap().clone();
469+
Meta::List(meta_list) => {
470+
let path = meta_list.path.get_ident().unwrap().clone();
460471
if path == ImageAttribute::SAMPLER {
461-
if let Expr::Path(ExprPath { path, .. }) =
462-
&named_value.value
463-
{
464-
let sampler_result = SamplerType::try_from(
465-
path.get_ident().unwrap().to_string(),
466-
);
472+
let sampler_meta_list = meta_list
473+
.parse_args_with(
474+
Punctuated::<Meta, Token![,]>::parse_terminated,
475+
)
476+
.unwrap();
477+
for attribute in &sampler_meta_list {
478+
match attribute {
479+
Meta::NameValue(named_value) => {
480+
let path = named_value
481+
.path
482+
.get_ident()
483+
.unwrap()
484+
.clone();
485+
if path == SamplerAttribute::FILTER {
486+
if let Expr::Path(ExprPath {
487+
path, ..
488+
}) = &named_value.value
489+
{
490+
let filter_result =
491+
FilterType::try_from(
492+
path.get_ident()
493+
.unwrap()
494+
.to_string(),
495+
);
467496

468-
if let Ok(sampler) = sampler_result {
469-
builder.sampler = Some(sampler);
470-
} else {
471-
errors.push(ParseFieldError::UnknownAttribute(
472-
named_value.value.into_token_stream(),
473-
));
497+
if let Ok(filter) = filter_result {
498+
builder.filter = Some(filter);
499+
} else {
500+
errors.push(ParseFieldError::UnknownAttribute(
501+
named_value.value.clone().into_token_stream(),
502+
));
503+
}
504+
} else {
505+
errors.push(
506+
ParseFieldError::WrongAttributeType(
507+
named_value.into_token_stream(),
508+
"path",
509+
),
510+
);
511+
}
512+
}
513+
}
514+
Meta::Path(path) => {
515+
let path = path.get_ident().unwrap().clone();
516+
if path == SamplerAttribute::CLAMP {
517+
builder.wrap = Some(WrapMode::Clamp);
518+
} else if path == SamplerAttribute::REPEAT {
519+
builder.wrap = Some(WrapMode::Repeat);
520+
} else {
521+
errors.push(
522+
ParseFieldError::UnknownAttribute(
523+
path.into_token_stream(),
524+
),
525+
);
526+
}
527+
}
528+
Meta::List(_) => {
529+
errors.push(
530+
ParseFieldError::WrongAttributeType(
531+
sampler_meta_list
532+
.clone()
533+
.into_token_stream(),
534+
"name-value or path",
535+
),
536+
);
537+
}
474538
}
475-
} else {
476-
errors.push(ParseFieldError::WrongAttributeType(
477-
named_value.into_token_stream(),
478-
"path",
479-
));
480539
}
481-
} else if path == ImageAttribute::LAYERS {
540+
}
541+
}
542+
Meta::NameValue(named_value) => {
543+
let path = named_value.path.get_ident().unwrap().clone();
544+
if path == ImageAttribute::LAYERS {
482545
if let Expr::Lit(ExprLit {
483546
lit: Lit::Int(layers),
484547
..
@@ -492,6 +555,10 @@ fn parse_field(field: &Field) -> Result<AssetField, Vec<ParseFieldError>> {
492555
"u32",
493556
));
494557
}
558+
} else {
559+
errors.push(ParseFieldError::UnknownAttributeType(
560+
path.into_token_stream(),
561+
));
495562
}
496563
}
497564
_ => {

0 commit comments

Comments
 (0)