Skip to content

Commit

Permalink
Make address mode a named value and support it for dynamic assets
Browse files Browse the repository at this point in the history
  • Loading branch information
NiklasEi committed Nov 25, 2024
1 parent 810d178 commit b80b26a
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 50 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ struct ImageAssets {
tree_nearest: Handle<Image>,

#[asset(path = "images/pixel_tree.png")]
#[asset(image(sampler(filter = linear, repeat)))]
#[asset(image(sampler(filter = linear, wrap = repeat)))]
tree_linear_repeat: Handle<Image>,
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
Image(
path: "images/female_adventurer_sheet.png",
sampler: Nearest,
wrap: Repeat,
),
Image(
path: "images/female_adventurer_sheet.png",
Expand Down
5 changes: 5 additions & 0 deletions bevy_asset_loader/assets/full_dynamic_collection.assets.ron
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
path: "images/tree.png",
sampler: Nearest
),
"pixel_tree_repeat": Image (
path: "images/tree.png",
sampler: Nearest,
wrap: Repeat
),
"array_texture": Image (
path: "images/array_texture.png",
array_texture_layers: 4
Expand Down
23 changes: 21 additions & 2 deletions bevy_asset_loader/examples/full_dynamic_collection.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use bevy::app::AppExit;
use bevy::asset::RecursiveDependencyLoadState;
use bevy::prelude::*;
use bevy::render::texture::{ImageSampler, ImageSamplerDescriptor};
use bevy::render::texture::{ImageAddressMode, ImageSampler, ImageSamplerDescriptor};
use bevy::utils::HashMap;
use bevy_asset_loader::prelude::*;

Expand Down Expand Up @@ -54,6 +54,9 @@ struct MyAssets {
// Image asset with sampler nearest (good for crisp pixel art)
#[asset(key = "pixel_tree")]
image_tree_nearest: Handle<Image>,
// Image asset with sampler nearest and address mode repeat
#[asset(key = "pixel_tree_repeat")]
image_tree_nearest_repeat: Handle<Image>,
// Array texture
#[asset(key = "array_texture")]
array_texture: Handle<Image>,
Expand Down Expand Up @@ -144,12 +147,28 @@ fn expectations(
.get(&assets.image_tree_nearest)
.expect("Image should be added to its asset resource");
let ImageSampler::Descriptor(descriptor) = &image.sampler else {
panic!("Descriptor was not set to non default value nearest");
panic!("Descriptor was not set to non default value");
};
assert_eq!(
descriptor.as_wgpu(),
ImageSamplerDescriptor::nearest().as_wgpu()
);
let image = images
.get(&assets.image_tree_nearest_repeat)
.expect("Image should be added to its asset resource");
let ImageSampler::Descriptor(descriptor) = &image.sampler else {
panic!("Descriptor was not set to non default value");
};
assert_eq!(
descriptor.as_wgpu(),
ImageSamplerDescriptor {
address_mode_u: ImageAddressMode::Repeat,
address_mode_v: ImageAddressMode::Repeat,
address_mode_w: ImageAddressMode::Repeat,
..ImageSamplerDescriptor::nearest()
}
.as_wgpu()
);

let image = images
.get(&assets.array_texture)
Expand Down
47 changes: 38 additions & 9 deletions bevy_asset_loader/examples/image_asset.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use bevy::math::Affine2;
use bevy::prelude::*;
use bevy_asset_loader::prelude::*;

/// This example demonstrates how you can set a different sampler for an [`Image`].
/// This example demonstrates how you can set different samplers and wrap modes for
/// an [`Image`] asset.
fn main() {
App::new()
.add_plugins(DefaultPlugins)
Expand All @@ -25,31 +27,58 @@ struct ImageAssets {
#[asset(image(sampler(filter = nearest)))]
tree_nearest: Handle<Image>,

#[asset(path = "images/pixel_tree.png")]
#[asset(image(sampler(filter = nearest, wrap = repeat)))]
tree_nearest_repeat: Handle<Image>,

#[asset(path = "images/array_texture.png")]
#[asset(image(array_texture_layers = 4))]
array_texture: Handle<Image>,
}

fn draw(mut commands: Commands, image_assets: Res<ImageAssets>) {
commands.spawn(Camera2dBundle {
projection: OrthographicProjection {
far: 1000.,
near: -1000.,
scale: 0.25,
fn draw(
mut commands: Commands,
image_assets: Res<ImageAssets>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(0.0, 1.5, 4.0).looking_at(Vec3::ZERO, Vec3::Y),
camera: Camera {
order: 1,
..default()
},
..default()
});
commands.spawn(Camera2dBundle::default());
commands.spawn(SpriteBundle {
texture: image_assets.tree_linear.clone(),
transform: Transform::from_translation(Vec3::new(-50., 0., 1.)),
transform: Transform::from_translation(Vec3::new(-50., 0., 1.)).with_scale(Vec3::splat(5.)),
..Default::default()
});
commands.spawn(SpriteBundle {
texture: image_assets.tree_nearest.clone(),
transform: Transform::from_translation(Vec3::new(50., 0., 1.)),
transform: Transform::from_translation(Vec3::new(50., 0., 1.)).with_scale(Vec3::splat(5.)),
..Default::default()
});
commands.spawn(PbrBundle {
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
material: materials.add(StandardMaterial {
base_color_texture: Some(image_assets.tree_nearest_repeat.clone()),
uv_transform: Affine2::from_scale(Vec2::new(2., 3.)),
..default()
}),
transform: Transform::from_xyz(1.5, 0.0, 0.0),
..default()
});
commands.spawn(PointLightBundle {
point_light: PointLight {
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
}

fn assert(images: Res<ImageAssets>, image_assets: Res<Assets<Image>>) {
Expand Down
62 changes: 45 additions & 17 deletions bevy_asset_loader/src/standard_dynamic_asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use bevy::sprite::TextureAtlasLayout;

#[cfg(any(feature = "3d", feature = "2d"))]
use bevy::render::texture::{Image, ImageSampler, ImageSamplerDescriptor};
use bevy::render::texture::{ImageAddressMode, ImageFilterMode};

/// These asset variants can be loaded from configuration files. They will then replace
/// a dynamic asset based on their keys.
Expand Down Expand Up @@ -48,8 +49,11 @@ pub enum StandardDynamicAsset {
/// Image file path
path: String,
/// Sampler
#[serde(with = "optional", skip_serializing_if = "Option::is_none", default)]
sampler: Option<ImageSamplerType>,
#[serde(default, skip_serializing_if = "is_default")]
sampler: ImageSamplerType,
/// Sampler
#[serde(default, skip_serializing_if = "is_default")]
wrap: ImageAddressModeType,
/// array texture layers
#[serde(with = "optional", skip_serializing_if = "Option::is_none", default)]
array_texture_layers: Option<u32>,
Expand Down Expand Up @@ -111,32 +115,48 @@ mod optional {
}
}

fn is_default<T: Default + PartialEq + Copy>(value: &T) -> bool {
T::default() == *value
}

/// Define the image sampler to configure for an image asset
#[cfg(any(feature = "3d", feature = "2d"))]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Default)]
pub enum ImageSamplerType {
/// See [`ImageSampler::nearest`]
#[default]
Nearest,
/// See [`ImageSampler::linear`]
Linear,
}

#[cfg(any(feature = "3d", feature = "2d"))]
impl From<ImageSamplerType> for ImageSamplerDescriptor {
fn from(value: ImageSamplerType) -> Self {
impl From<&ImageSamplerType> for ImageFilterMode {
fn from(value: &ImageSamplerType) -> Self {
match value {
ImageSamplerType::Nearest => ImageSamplerDescriptor::nearest(),
ImageSamplerType::Linear => ImageSamplerDescriptor::linear(),
ImageSamplerType::Nearest => ImageFilterMode::Nearest,
ImageSamplerType::Linear => ImageFilterMode::Linear,
}
}
}

/// Define the image sampler address mode
#[cfg(any(feature = "3d", feature = "2d"))]
impl From<ImageSamplerType> for ImageSampler {
fn from(value: ImageSamplerType) -> Self {
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Default)]
pub enum ImageAddressModeType {
/// See [`ImageAddressMode::ClampToEdge`]
#[default]
ClampToEdge,
/// See [`ImageAddressMode::Repeat`]
Repeat,
}

#[cfg(any(feature = "3d", feature = "2d"))]
impl From<&ImageAddressModeType> for ImageAddressMode {
fn from(value: &ImageAddressModeType) -> Self {
match value {
ImageSamplerType::Nearest => ImageSampler::nearest(),
ImageSamplerType::Linear => ImageSampler::linear(),
ImageAddressModeType::ClampToEdge => ImageAddressMode::ClampToEdge,
ImageAddressModeType::Repeat => ImageAddressMode::Repeat,
}
}
}
Expand Down Expand Up @@ -179,15 +199,14 @@ impl DynamicAsset for StandardDynamicAsset {
StandardDynamicAsset::Image {
path,
sampler,
wrap: address_mode,
array_texture_layers,
} => {
let mut system_state =
SystemState::<(ResMut<Assets<Image>>, Res<AssetServer>)>::new(world);
let (mut images, asset_server) = system_state.get_mut(world);
let mut handle = asset_server.load(path);
if let Some(sampler) = sampler {
Self::update_image_sampler(&mut handle, &mut images, sampler);
}
Self::update_image_sampler(&mut handle, &mut images, sampler, address_mode);
if let Some(layers) = array_texture_layers {
let image = images
.get_mut(&handle)
Expand Down Expand Up @@ -273,21 +292,30 @@ impl StandardDynamicAsset {
handle: &mut bevy::asset::Handle<Image>,
images: &mut Assets<Image>,
sampler_type: &ImageSamplerType,
address_mode: &ImageAddressModeType,
) {
let image = images.get_mut(&*handle).unwrap();
let configured_descriptor = ImageSamplerDescriptor {
address_mode_u: address_mode.into(),
address_mode_v: address_mode.into(),
address_mode_w: address_mode.into(),
mag_filter: sampler_type.into(),
min_filter: sampler_type.into(),
mipmap_filter: sampler_type.into(),
..Default::default()
};
let is_different_sampler = if let ImageSampler::Descriptor(descriptor) = &image.sampler {
let configured_descriptor: ImageSamplerDescriptor = sampler_type.clone().into();
!descriptor.as_wgpu().eq(&configured_descriptor.as_wgpu())
} else {
false
};

if is_different_sampler {
let mut cloned_image = image.clone();
cloned_image.sampler = sampler_type.clone().into();
cloned_image.sampler = ImageSampler::Descriptor(configured_descriptor);
*handle = images.add(cloned_image);
} else {
image.sampler = sampler_type.clone().into();
image.sampler = ImageSampler::Descriptor(configured_descriptor);
}
}
}
Expand Down
15 changes: 12 additions & 3 deletions bevy_asset_loader_derive/src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,23 @@ impl TryFrom<String> for FilterType {
}
}

#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(crate) enum WrapMode {
#[default]
Clamp,
#[allow(dead_code)]
Repeat,
}

impl TryFrom<String> for WrapMode {
type Error = &'static str;
fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_str() {
"clamp" => Ok(Self::Clamp),
"repeat" => Ok(Self::Repeat),
_ => Err("Value must be either `clamp` or `repeat`"),
}
}
}

#[derive(PartialEq, Debug)]
pub(crate) struct ImageAssetField {
pub field_ident: Ident,
Expand Down
47 changes: 29 additions & 18 deletions bevy_asset_loader_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,7 @@ impl SamplerAttribute {
#[allow(dead_code)]
pub const FILTER: &'static str = "filter";
#[allow(dead_code)]
pub const CLAMP: &'static str = "clamp";
#[allow(dead_code)]
pub const REPEAT: &'static str = "repeat";
pub const WRAP: &'static str = "wrap";
}

pub(crate) const COLLECTION_ATTRIBUTE: &str = "collection";
Expand Down Expand Up @@ -510,28 +508,41 @@ fn parse_field(field: &Field) -> Result<AssetField, Vec<ParseFieldError>> {
);
}
}
}
Meta::Path(path) => {
let path = path.get_ident().unwrap().clone();
if path == SamplerAttribute::CLAMP {
builder.wrap = Some(WrapMode::Clamp);
} else if path == SamplerAttribute::REPEAT {
builder.wrap = Some(WrapMode::Repeat);
} else {
errors.push(
ParseFieldError::UnknownAttribute(
path.into_token_stream(),
),
);
if path == SamplerAttribute::WRAP {
if let Expr::Path(ExprPath {
path, ..
}) = &named_value.value
{
let wrap_result = WrapMode::try_from(
path.get_ident()
.unwrap()
.to_string(),
);

if let Ok(wrap) = wrap_result {
builder.wrap = Some(wrap);
} else {
errors.push(ParseFieldError::UnknownAttribute(
named_value.value.clone().into_token_stream(),
));
}
} else {
errors.push(
ParseFieldError::WrongAttributeType(
named_value.into_token_stream(),
"path",
),
);
}
}
}
Meta::List(_) => {
Meta::List(_) | Meta::Path(_) => {
errors.push(
ParseFieldError::WrongAttributeType(
sampler_meta_list
.clone()
.into_token_stream(),
"name-value or path",
"name-value",
),
);
}
Expand Down

0 comments on commit b80b26a

Please sign in to comment.