diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index c699a58945ad8..5251042851efa 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -25,7 +25,7 @@ use bevy_render::{ primitives::{Aabb, Frustum}, render_resource::{AddressMode, Face, FilterMode, PrimitiveTopology, SamplerDescriptor}, renderer::RenderDevice, - texture::{CompressedImageFormats, Image, ImageType, TextureError}, + texture::{CompressedImageFormats, Image, ImageSampler, ImageType, TextureError}, view::VisibleEntities, }; use bevy_scene::Scene; @@ -619,7 +619,7 @@ async fn load_texture<'a>( )? } }; - texture.sampler_descriptor = texture_sampler(&gltf_texture); + texture.sampler_descriptor = ImageSampler::Descriptor(texture_sampler(&gltf_texture)); Ok((texture, texture_label(&gltf_texture))) } diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 204c44c3bd45a..449e711100002 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -7,7 +7,7 @@ use bevy_app::Plugin; use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped}; use bevy_ecs::{ prelude::*, - system::{lifetimeless::*, SystemParamItem}, + system::{lifetimeless::*, SystemParamItem, SystemState}, }; use bevy_math::{Mat4, Vec2}; use bevy_reflect::TypeUuid; @@ -21,7 +21,9 @@ use bevy_render::{ render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::*, renderer::{RenderDevice, RenderQueue}, - texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo}, + texture::{ + BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, + }, view::{ComputedVisibility, ViewUniform, ViewUniformOffset, ViewUniforms}, RenderApp, RenderStage, }; @@ -275,7 +277,12 @@ pub struct MeshPipeline { impl FromWorld for MeshPipeline { fn from_world(world: &mut World) -> Self { - let render_device = world.resource::(); + let mut system_state: SystemState<( + Res, + Res, + Res, + )> = SystemState::new(world); + let (render_device, default_sampler, render_queue) = system_state.get_mut(world); let clustered_forward_buffer_binding_type = render_device .get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); @@ -435,10 +442,12 @@ impl FromWorld for MeshPipeline { TextureFormat::bevy_default(), ); let texture = render_device.create_texture(&image.texture_descriptor); - let sampler = render_device.create_sampler(&image.sampler_descriptor); + let sampler = match image.sampler_descriptor { + ImageSampler::Default => (**default_sampler).clone(), + ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor), + }; let format_size = image.texture_descriptor.format.pixel_size(); - let render_queue = world.resource_mut::(); render_queue.write_texture( ImageCopyTexture { texture: &texture, diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 888d0d6865b96..0caa1a75bc29c 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -180,7 +180,8 @@ impl Plugin for RenderPlugin { .insert_resource(queue) .insert_resource(adapter_info) .insert_resource(pipeline_cache) - .insert_resource(asset_server); + .insert_resource(asset_server) + .init_resource::(); app.add_sub_app(RenderApp, render_app, move |app_world, render_app| { #[cfg(feature = "trace")] diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index 6253913d7fac5..f898140acbd73 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -13,9 +13,11 @@ use crate::{ texture::BevyDefault, }; use bevy_asset::HandleUntyped; +use bevy_derive::{Deref, DerefMut}; use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_math::Vec2; use bevy_reflect::TypeUuid; +use std::hash::Hash; use thiserror::Error; use wgpu::{ Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, TextureDimension, TextureFormat, @@ -106,9 +108,49 @@ pub struct Image { pub data: Vec, // TODO: this nesting makes accessing Image metadata verbose. Either flatten out descriptor or add accessors pub texture_descriptor: wgpu::TextureDescriptor<'static>, - pub sampler_descriptor: wgpu::SamplerDescriptor<'static>, + pub sampler_descriptor: ImageSampler, } +/// Used in `Image`, this determines what image sampler to use when rendering. The default setting, +/// [`ImageSampler::Default`], will result in reading the sampler set in the [`DefaultImageSampler`] +/// resource - the global default sampler - at runtime. Setting this to [`ImageSampler::Descriptor`] +/// will override the global default descriptor for this [`Image`]. +#[derive(Debug, Clone)] +pub enum ImageSampler { + Default, + Descriptor(wgpu::SamplerDescriptor<'static>), +} +impl Default for ImageSampler { + fn default() -> Self { + Self::Default + } +} + +impl ImageSampler { + /// Returns a sampler descriptor with `Linear` min and mag filters + pub fn linear_descriptor() -> wgpu::SamplerDescriptor<'static> { + wgpu::SamplerDescriptor { + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + ..Default::default() + } + } + + /// Returns a sampler descriptor with `Nearest` min and mag filters + pub fn nearest_descriptor() -> wgpu::SamplerDescriptor<'static> { + wgpu::SamplerDescriptor { + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + ..Default::default() + } + } +} + +/// Resource used as the global default image sampler for [`Image`]s with their `sampler_descriptor` +/// set to [`ImageSampler::Default`]. +#[derive(Debug, Clone, Deref, DerefMut)] +pub struct DefaultImageSampler(pub(crate) Sampler); + impl Default for Image { fn default() -> Self { let format = wgpu::TextureFormat::bevy_default(); @@ -128,7 +170,7 @@ impl Default for Image { sample_count: 1, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, }, - sampler_descriptor: wgpu::SamplerDescriptor::default(), + sampler_descriptor: ImageSampler::Default, } } } @@ -540,7 +582,11 @@ pub struct GpuImage { impl RenderAsset for Image { type ExtractedAsset = Image; type PreparedAsset = GpuImage; - type Param = (SRes, SRes); + type Param = ( + SRes, + SRes, + SRes, + ); /// Clones the Image. fn extract_asset(&self) -> Self::ExtractedAsset { @@ -550,7 +596,7 @@ impl RenderAsset for Image { /// Converts the extracted image into a [`GpuImage`]. fn prepare_asset( image: Self::ExtractedAsset, - (render_device, render_queue): &mut SystemParamItem, + (render_device, render_queue, default_sampler): &mut SystemParamItem, ) -> Result> { let texture = if image.texture_descriptor.mip_level_count > 1 || image.is_compressed() { render_device.create_texture_with_data( @@ -593,7 +639,11 @@ impl RenderAsset for Image { image.texture_descriptor.size.width as f32, image.texture_descriptor.size.height as f32, ); - let sampler = render_device.create_sampler(&image.sampler_descriptor); + let sampler = match image.sampler_descriptor { + ImageSampler::Default => (***default_sampler).clone(), + ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor), + }; + Ok(GpuImage { texture, texture_view, diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 851eac1371b9e..1ca625d1a2cfc 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -26,6 +26,7 @@ pub use texture_cache::*; use crate::{ render_asset::{PrepareAssetLabel, RenderAssetPlugin}, + renderer::RenderDevice, RenderApp, RenderStage, }; use bevy_app::{App, Plugin}; @@ -63,14 +64,52 @@ impl Plugin for ImagePlugin { .resource_mut::>() .set_untracked(DEFAULT_IMAGE_HANDLE, Image::default()); + let default_sampler = app + .world + .get_resource_or_insert_with(ImageSettings::default) + .default_sampler + .clone(); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + let default_sampler = { + let device = render_app.world.resource::(); + device.create_sampler(&default_sampler) + }; render_app + .insert_resource(DefaultImageSampler(default_sampler)) .init_resource::() .add_system_to_stage(RenderStage::Cleanup, update_texture_cache_system); } } } +/// [`ImagePlugin`] settings. +pub struct ImageSettings { + /// The default image sampler to use when [`ImageSampler`] is set to `Default`. + pub default_sampler: wgpu::SamplerDescriptor<'static>, +} + +impl Default for ImageSettings { + fn default() -> Self { + ImageSettings::default_linear() + } +} + +impl ImageSettings { + /// Creates image settings with default linear sampling. + pub fn default_linear() -> ImageSettings { + ImageSettings { + default_sampler: ImageSampler::linear_descriptor(), + } + } + + /// Creates image settings with default nearest sampling. + pub fn default_nearest() -> ImageSettings { + ImageSettings { + default_sampler: ImageSampler::nearest_descriptor(), + } + } +} + pub trait BevyDefault { fn bevy_default() -> Self; } diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index c0c3e3010c302..6dc1ce9d0d6b1 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -2,7 +2,7 @@ use bevy_app::Plugin; use bevy_asset::{load_internal_asset, Handle, HandleUntyped}; use bevy_ecs::{ prelude::*, - system::{lifetimeless::*, SystemParamItem}, + system::{lifetimeless::*, SystemParamItem, SystemState}, }; use bevy_math::{Mat4, Vec2}; use bevy_reflect::{Reflect, TypeUuid}; @@ -13,7 +13,9 @@ use bevy_render::{ render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::*, renderer::{RenderDevice, RenderQueue}, - texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo}, + texture::{ + BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, + }, view::{ComputedVisibility, ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms}, RenderApp, RenderStage, }; @@ -140,7 +142,9 @@ pub struct Mesh2dPipeline { impl FromWorld for Mesh2dPipeline { fn from_world(world: &mut World) -> Self { - let render_device = world.resource::(); + let mut system_state: SystemState<(Res, Res)> = + SystemState::new(world); + let (render_device, default_sampler) = system_state.get_mut(world); let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ // View @@ -180,7 +184,10 @@ impl FromWorld for Mesh2dPipeline { TextureFormat::bevy_default(), ); let texture = render_device.create_texture(&image.texture_descriptor); - let sampler = render_device.create_sampler(&image.sampler_descriptor); + let sampler = match image.sampler_descriptor { + ImageSampler::Default => (**default_sampler).clone(), + ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor), + }; let format_size = image.texture_descriptor.format.pixel_size(); let render_queue = world.resource_mut::(); diff --git a/examples/2d/sprite_sheet.rs b/examples/2d/sprite_sheet.rs index 43ad1deb7dd0d..4286b88a24737 100644 --- a/examples/2d/sprite_sheet.rs +++ b/examples/2d/sprite_sheet.rs @@ -1,10 +1,11 @@ //! Renders an animated sprite by loading all animation frames from a single image (a sprite sheet) //! into a texture atlas, and changing the displayed image periodically. -use bevy::prelude::*; +use bevy::{prelude::*, render::texture::ImageSettings}; fn main() { App::new() + .insert_resource(ImageSettings::default_nearest()) // prevents blurry sprites .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_system(animate_sprite) diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index 5daabe22e116c..74599ab9295a5 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -1,11 +1,12 @@ //! In this example we generate a new texture atlas (sprite sheet) from a folder containing //! individual sprites. -use bevy::{asset::LoadState, prelude::*}; +use bevy::{asset::LoadState, prelude::*, render::texture::ImageSettings}; fn main() { App::new() .init_resource::() + .insert_resource(ImageSettings::default_nearest()) // prevents blurry sprites .add_plugins(DefaultPlugins) .add_state(AppState::Setup) .add_system_set(SystemSet::on_enter(AppState::Setup).with_system(load_textures))