Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting a sampler for an Image asset #156

Merged
merged 7 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bevy_asset_loader/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,7 @@ required-features = ["2d", "3d", "standard_dynamic_assets"]
[[example]]
name = "custom_dynamic_assets"
path = "examples/custom_dynamic_assets.rs"

[[example]]
name = "image_asset"
path = "examples/image_asset.rs"
55 changes: 55 additions & 0 deletions bevy_asset_loader/examples/image_asset.rs
st0rmbtw marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use bevy::prelude::*;
use bevy_asset_loader::prelude::*;

/// This example demonstrates how you can use [`App::init_resource_after_loading_state`] to initialize
st0rmbtw marked this conversation as resolved.
Show resolved Hide resolved
/// assets implementing [`FromWorld`] after your collections are inserted into the ECS.
///
/// In this showcase we load two images in an [`AssetCollection`] and then combine
/// them by adding up their pixel data.
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_state::<MyStates>()
.add_loading_state(
LoadingState::new(MyStates::AssetLoading).continue_to_state(MyStates::Next),
)
.add_collection_to_loading_state::<_, ImageAssets>(MyStates::AssetLoading)
.insert_resource(Msaa::Off)
.add_systems(OnEnter(MyStates::Next), draw)
.run();
}

#[derive(AssetCollection, Resource)]
struct ImageAssets {
#[asset(path = "images/player.png")]
st0rmbtw marked this conversation as resolved.
Show resolved Hide resolved
#[asset(image(sampler = linear))]
player: Handle<Image>,

#[asset(path = "images/tree.png")]
#[asset(image(sampler = nearest))]
tree: Handle<Image>,
}

fn draw(
mut commands: Commands,
image_assets: Res<ImageAssets>,
) {
commands.spawn(Camera2dBundle::default());
commands.spawn(SpriteBundle {
texture: image_assets.player.clone(),
transform: Transform::from_translation(Vec3::new(-150., 0., 1.)),
..Default::default()
});
commands.spawn(SpriteBundle {
texture: image_assets.tree.clone(),
transform: Transform::from_translation(Vec3::new(150., 0., 1.)),
..Default::default()
});
}

#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]
enum MyStates {
#[default]
AssetLoading,
Next,
}
97 changes: 97 additions & 0 deletions bevy_asset_loader_derive/src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ pub(crate) struct TextureAtlasAssetField {
pub offset_y: f32,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(crate) enum SamplerType {
Linear,
Nearest
}

#[derive(PartialEq, Debug)]
pub(crate) struct ImageAssetField {
pub field_ident: Ident,
pub asset_path: String,
pub sampler: SamplerType,
}

#[derive(PartialEq, Debug)]
pub(crate) struct BasicAssetField {
pub field_ident: Ident,
Expand Down Expand Up @@ -43,6 +56,7 @@ pub(crate) enum AssetField {
Folder(BasicAssetField, Typed, Mapped),
Files(MultipleFilesField, Typed, Mapped),
TextureAtlas(TextureAtlasAssetField),
Image(ImageAssetField),
StandardMaterial(BasicAssetField),
Dynamic(DynamicAssetField),
OptionalDynamic(DynamicAssetField),
Expand Down Expand Up @@ -94,6 +108,40 @@ impl AssetField {
asset_server.get_handle(#asset_path)
},)
}
AssetField::Image(image) => {
let field_ident = image.field_ident.clone();
let asset_path = image.asset_path.clone();
match image.sampler {
st0rmbtw marked this conversation as resolved.
Show resolved Hide resolved
SamplerType::Linear => {
quote!(#token_stream #field_ident : {
use bevy::render::texture::ImageSampler;

let cell = world.cell();
let asset_server = cell.get_resource::<AssetServer>().expect("Cannot get AssetServer");
let mut images = cell.get_resource_mut::<Assets<Image>>().expect("Cannot get resource Assets<Image>");

let handle = asset_server.get_handle(#asset_path);
let mut image = images.get_mut(&handle).unwrap();
st0rmbtw marked this conversation as resolved.
Show resolved Hide resolved
image.sampler_descriptor = ImageSampler::linear();
handle
},)
},
SamplerType::Nearest => {
quote!(#token_stream #field_ident : {
use bevy::render::texture::ImageSampler;

let cell = world.cell();
let asset_server = cell.get_resource::<AssetServer>().expect("Cannot get AssetServer");
let mut images = cell.get_resource_mut::<Assets<Image>>().expect("Cannot get resource Assets<Image>");

let handle = asset_server.get_handle(#asset_path);
let mut image = images.get_mut(&handle).unwrap();
image.sampler_descriptor = ImageSampler::nearest();
handle
},)
}
}
}
AssetField::Folder(basic, typed, mapped) => {
let field_ident = basic.field_ident.clone();
let asset_path = basic.asset_path.clone();
Expand Down Expand Up @@ -422,6 +470,10 @@ impl AssetField {
let asset_path = asset.asset_path.clone();
quote!(#token_stream handles.push(asset_server.load_untyped(#asset_path));)
}
AssetField::Image(asset) => {
let asset_path = asset.asset_path.clone();
quote!(#token_stream handles.push(asset_server.load_untyped(#asset_path));)
}
AssetField::Files(assets, _, _) => {
let asset_paths = assets.asset_paths.clone();
quote!(#token_stream #(handles.push(asset_server.load_untyped(#asset_paths)));*;)
Expand Down Expand Up @@ -449,6 +501,7 @@ pub(crate) struct AssetBuilder {
pub padding_y: Option<f32>,
pub offset_x: Option<f32>,
pub offset_y: Option<f32>,
pub sampler: Option<SamplerType>
}

impl AssetBuilder {
Expand Down Expand Up @@ -557,6 +610,13 @@ impl AssetBuilder {
self.is_mapped.into(),
));
}
if self.sampler.is_some() {
return Ok(AssetField::Image(ImageAssetField {
field_ident: self.field_ident.unwrap(),
asset_path: self.asset_path.unwrap(),
sampler: self.sampler.unwrap()
}))
}
let asset = BasicAssetField {
field_ident: self.field_ident.unwrap(),
asset_path: self.asset_path.unwrap(),
Expand Down Expand Up @@ -825,6 +885,43 @@ mod test {
);
}

#[test]
fn image_asset() {
let builder_linear = AssetBuilder {
field_ident: Some(Ident::new("test", Span::call_site())),
asset_path: Some("some/image.png".to_owned()),
sampler: Some(SamplerType::Linear),
..Default::default()
};

let builder_nearest = AssetBuilder {
field_ident: Some(Ident::new("test", Span::call_site())),
asset_path: Some("some/image.png".to_owned()),
sampler: Some(SamplerType::Nearest),
..Default::default()
};

let asset_linear = builder_linear.build().expect("This should be a valid ImageAsset");
let asset_nearest = builder_nearest.build().expect("This should be a valid ImageAsset");

assert_eq!(
asset_linear,
AssetField::Image(ImageAssetField {
field_ident: Ident::new("test", Span::call_site()),
asset_path: "some/image.png".to_owned(),
sampler: SamplerType::Linear
})
);
assert_eq!(
asset_nearest,
AssetField::Image(ImageAssetField {
field_ident: Ident::new("test", Span::call_site()),
asset_path: "some/image.png".to_owned(),
sampler: SamplerType::Nearest
})
);
}

#[test]
fn dynamic_asset_does_only_accept_some_attributes() {
let mut builder = asset_builder_dynamic();
Expand Down
49 changes: 48 additions & 1 deletion bevy_asset_loader_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::assets::*;
use proc_macro2::Ident;
use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
use syn::punctuated::Punctuated;
use syn::{Data, Expr, ExprLit, Field, Fields, Index, Lit, LitStr, Meta, Token};
use syn::{Data, Expr, ExprLit, Field, Fields, Index, Lit, LitStr, Meta, Token, ExprPath};

/// Derive macro for [`AssetCollection`]
///
Expand Down Expand Up @@ -54,6 +54,12 @@ impl TextureAtlasAttribute {
pub const OFFSET_Y: &'static str = "offset_y";
}

pub(crate) const IMAGE_ATTRIBUTE: &str = "image";
pub(crate) struct ImageAttribute;
impl ImageAttribute {
pub const SAMPLER: &'static str = "sampler";
}

pub(crate) const COLLECTION_ATTRIBUTE: &str = "collection";
pub(crate) const PATHS_ATTRIBUTE: &str = "paths";
pub(crate) const TYPED_ATTRIBUTE: &str = "typed";
Expand Down Expand Up @@ -421,6 +427,47 @@ fn parse_field(field: &Field) -> Result<AssetField, Vec<ParseFieldError>> {
}
builder.asset_paths = Some(paths);
}
Meta::List(meta_list) if meta_list.path.is_ident(IMAGE_ATTRIBUTE) => {
let image_meta_list =
meta_list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated);

for attribute in image_meta_list.unwrap() {
match attribute {
Meta::NameValue(named_value) => {
let path = named_value.path.get_ident().unwrap().clone();
if path == ImageAttribute::SAMPLER {
if let Expr::Path(ExprPath {
path,
..
}) = &named_value.value
{
let sampler = match path.get_ident().unwrap().to_string().as_str() {
st0rmbtw marked this conversation as resolved.
Show resolved Hide resolved
"linear" => Some(SamplerType::Linear),
"nearest" => Some(SamplerType::Nearest),
_ => None
};

if sampler.is_none() {
errors.push(ParseFieldError::UnknownAttribute(named_value.value.into_token_stream()));
}

builder.sampler = sampler;
} else {
errors.push(ParseFieldError::WrongAttributeType(
named_value.into_token_stream(),
"path",
));
}
}
},
_ => {
errors.push(ParseFieldError::UnknownAttributeType(
attribute.into_token_stream(),
));
}
}
}
},
Meta::List(meta_list) => errors.push(ParseFieldError::UnknownAttribute(
meta_list.into_token_stream(),
)),
Expand Down
Loading