From f1a16420ad230ec27c0db8f6b1db26cb1f3338e8 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 12 Apr 2022 18:05:06 -0700 Subject: [PATCH 1/8] Change default `Image` `FilterMode` to `Linear` --- crates/bevy_render/src/texture/image.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index fa4afe04924ef..970c2f7d2af6f 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -131,7 +131,11 @@ impl Default for Image { sample_count: 1, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, }, - sampler_descriptor: wgpu::SamplerDescriptor::default(), + sampler_descriptor: wgpu::SamplerDescriptor { + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + ..Default::default() + } } } } From f91eeecd9088d99325e26afc84ca5c6de1b3955e Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 12 Apr 2022 18:09:57 -0700 Subject: [PATCH 2/8] fmt fix --- crates/bevy_render/src/texture/image.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index 970c2f7d2af6f..6f8fb4639b3a4 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -135,7 +135,7 @@ impl Default for Image { mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, ..Default::default() - } + }, } } } From 7aac5ba8e2f72ae3609ae449ae295c1aba868ac3 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 12 Apr 2022 18:20:48 -0700 Subject: [PATCH 3/8] Add comments --- crates/bevy_render/src/texture/image.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index 6f8fb4639b3a4..72f86e4d2e6c6 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -132,6 +132,10 @@ impl Default for Image { usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, }, sampler_descriptor: wgpu::SamplerDescriptor { + // At the time of writing, wgpu defaults to `Nearest` filtering. This causes + // artifacts when the image is scaled. Using `Linear` results in a more uniform, + // though potentially blurry image. + // See issue for details: https://github.com/bevyengine/bevy/issues/4464 mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, ..Default::default() From 28ff62532100e8687d1efbbe9cf2a05c937df2fa Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 7 Jun 2022 03:21:49 -0700 Subject: [PATCH 4/8] Review feedback --- crates/bevy_gltf/src/loader.rs | 4 +- crates/bevy_pbr/src/render/mesh.rs | 9 +++- crates/bevy_render/src/lib.rs | 8 ++- crates/bevy_render/src/texture/image.rs | 66 +++++++++++++++++++------ crates/bevy_sprite/src/mesh2d/mesh.rs | 9 +++- examples/2d/sprite_sheet.rs | 3 +- examples/2d/texture_atlas.rs | 3 +- 7 files changed, 78 insertions(+), 24 deletions(-) diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 2b95f3e5541b4..71234a7de39da 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -26,7 +26,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; @@ -600,7 +600,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 7cff3f9d9fca8..35b8077eab170 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -21,7 +21,9 @@ use bevy_render::{ render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::{std140::AsStd140, *}, renderer::{RenderDevice, RenderQueue}, - texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo}, + texture::{ + BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, + }, view::{ComputedVisibility, ViewUniform, ViewUniformOffset, ViewUniforms}, RenderApp, RenderStage, }; @@ -427,7 +429,10 @@ 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 = render_device.create_sampler(match image.sampler_descriptor { + ImageSampler::Default => &world.resource::().0, + ImageSampler::Descriptor(ref d) => d, + }); let format_size = image.texture_descriptor.format.pixel_size(); let render_queue = world.resource_mut::(); diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 5d7127c0ce7b1..3feeaec39ec84 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -31,6 +31,7 @@ pub mod prelude { use bevy_utils::tracing::debug; pub use once_cell; +use texture::{extract_image_sampler, DefaultImageSampler}; use crate::{ camera::CameraPlugin, @@ -147,7 +148,8 @@ impl Plugin for RenderPlugin { .insert_resource(adapter_info.clone()) .init_resource::() .register_type::() - .register_type::(); + .register_type::() + .insert_resource(DefaultImageSampler::linear()); let pipeline_cache = PipelineCache::new(device.clone()); let asset_server = app.world.resource::().clone(); @@ -176,7 +178,9 @@ impl Plugin for RenderPlugin { .insert_resource(adapter_info) .insert_resource(pipeline_cache) .insert_resource(asset_server) - .init_resource::(); + .init_resource::() + .insert_resource(DefaultImageSampler::linear()) + .add_system_to_stage(RenderStage::Extract, extract_image_sampler); 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 72f86e4d2e6c6..068f1c3587ebe 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -13,7 +13,7 @@ use crate::{ texture::BevyDefault, }; use bevy_asset::HandleUntyped; -use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; +use bevy_ecs::system::{lifetimeless::SRes, Commands, Res, SystemParamItem}; use bevy_math::{Size, Vec2}; use bevy_reflect::TypeUuid; use thiserror::Error; @@ -109,7 +109,45 @@ 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, +} + +#[derive(Debug, Clone)] +pub enum ImageSampler { + Default, + Descriptor(wgpu::SamplerDescriptor<'static>), +} +impl Default for ImageSampler { + fn default() -> Self { + Self::Default + } +} + +/// Resource used as the global default image sampler for [`Image`]s with their `sampler_descriptor` +/// set to [`ImageSampler::Default`]. Otherwise, the specified sampler in +/// [`ImageSampler::Descriptor`] will be used. +#[derive(Debug, Clone)] +pub struct DefaultImageSampler(pub wgpu::SamplerDescriptor<'static>); +impl DefaultImageSampler { + pub fn linear() -> Self { + DefaultImageSampler(wgpu::SamplerDescriptor { + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + ..Default::default() + }) + } + pub fn nearest() -> Self { + DefaultImageSampler(wgpu::SamplerDescriptor { + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }) + } +} + +pub fn extract_image_sampler(mut commands: Commands, sampler: Res) { + // NOTE: windows.is_changed() handles cases where a window was resized + commands.insert_resource(sampler.clone()); } impl Default for Image { @@ -131,15 +169,7 @@ impl Default for Image { sample_count: 1, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, }, - sampler_descriptor: wgpu::SamplerDescriptor { - // At the time of writing, wgpu defaults to `Nearest` filtering. This causes - // artifacts when the image is scaled. Using `Linear` results in a more uniform, - // though potentially blurry image. - // See issue for details: https://github.com/bevyengine/bevy/issues/4464 - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - ..Default::default() - }, + sampler_descriptor: ImageSampler::Default, } } } @@ -554,7 +584,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 { @@ -564,7 +598,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( @@ -607,7 +641,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 descriptor = match image.sampler_descriptor { + ImageSampler::Default => &default_sampler.0, + ImageSampler::Descriptor(ref d) => d, + }; + let sampler = render_device.create_sampler(descriptor); Ok(GpuImage { texture, texture_view, diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 4a804372b978a..664bbf628590c 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -13,7 +13,9 @@ use bevy_render::{ render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::{std140::AsStd140, *}, renderer::{RenderDevice, RenderQueue}, - texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo}, + texture::{ + BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, + }, view::{ComputedVisibility, ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms}, RenderApp, RenderStage, }; @@ -164,7 +166,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 = render_device.create_sampler(match image.sampler_descriptor { + ImageSampler::Default => &world.resource::().0, + ImageSampler::Descriptor(ref d) => d, + }); 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 d3d313fab226e..7d00a7b595577 100644 --- a/examples/2d/sprite_sheet.rs +++ b/examples/2d/sprite_sheet.rs @@ -1,8 +1,9 @@ -use bevy::prelude::*; +use bevy::{prelude::*, render::texture::DefaultImageSampler}; fn main() { App::new() .add_plugins(DefaultPlugins) + .insert_resource(DefaultImageSampler::nearest()) // prevents blurry sprites .add_startup_system(setup) .add_system(animate_sprite) .run(); diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index 8aa7bcae4329a..b78a0c0459d9e 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -1,4 +1,4 @@ -use bevy::{asset::LoadState, prelude::*}; +use bevy::{asset::LoadState, prelude::*, render::texture::DefaultImageSampler}; /// In this example we generate a new texture atlas (sprite sheet) from a folder containing /// individual sprites @@ -6,6 +6,7 @@ fn main() { App::new() .init_resource::() .add_plugins(DefaultPlugins) + .insert_resource(DefaultImageSampler::nearest()) // prevents blurry sprites .add_state(AppState::Setup) .add_system_set(SystemSet::on_enter(AppState::Setup).with_system(load_textures)) .add_system_set(SystemSet::on_update(AppState::Setup).with_system(check_textures)) From 54685b2d3ab04a840ea4d4e1fef259d307c62cde Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 7 Jun 2022 12:23:14 -0700 Subject: [PATCH 5/8] Refactor resource extraction --- crates/bevy_render/src/lib.rs | 8 ++------ crates/bevy_render/src/texture/image.rs | 23 +++++++++++++++++------ crates/bevy_render/src/texture/mod.rs | 4 ++++ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 892cf02827d14..0caa1a75bc29c 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -28,7 +28,6 @@ pub mod prelude { } pub use once_cell; -use texture::{extract_image_sampler, DefaultImageSampler}; use crate::{ camera::CameraPlugin, @@ -152,8 +151,7 @@ impl Plugin for RenderPlugin { .insert_resource(adapter_info.clone()) .init_resource::() .register_type::() - .register_type::() - .insert_resource(DefaultImageSampler::linear()); + .register_type::(); let pipeline_cache = PipelineCache::new(device.clone()); let asset_server = app.world.resource::().clone(); @@ -183,9 +181,7 @@ impl Plugin for RenderPlugin { .insert_resource(adapter_info) .insert_resource(pipeline_cache) .insert_resource(asset_server) - .init_resource::() - .insert_resource(DefaultImageSampler::linear()) - .add_system_to_stage(RenderStage::Extract, extract_image_sampler); + .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 3e0b1c43acf29..b9d0fc0ae50d2 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -7,13 +7,14 @@ use super::ktx2::*; use super::image_texture_conversion::image_to_texture; use crate::{ + extract_resource::ExtractResource, render_asset::{PrepareAssetError, RenderAsset}, render_resource::{Sampler, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, texture::BevyDefault, }; use bevy_asset::HandleUntyped; -use bevy_ecs::system::{lifetimeless::SRes, Commands, Res, SystemParamItem}; +use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_math::Vec2; use bevy_reflect::TypeUuid; use thiserror::Error; @@ -109,6 +110,10 @@ pub struct Image { 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, @@ -121,8 +126,7 @@ impl Default for ImageSampler { } /// Resource used as the global default image sampler for [`Image`]s with their `sampler_descriptor` -/// set to [`ImageSampler::Default`]. Otherwise, the specified sampler in -/// [`ImageSampler::Descriptor`] will be used. +/// set to [`ImageSampler::Default`]. #[derive(Debug, Clone)] pub struct DefaultImageSampler(pub wgpu::SamplerDescriptor<'static>); impl DefaultImageSampler { @@ -141,10 +145,17 @@ impl DefaultImageSampler { }) } } +impl ExtractResource for DefaultImageSampler { + type Source = DefaultImageSampler; -pub fn extract_image_sampler(mut commands: Commands, sampler: Res) { - // NOTE: windows.is_changed() handles cases where a window was resized - commands.insert_resource(sampler.clone()); + fn extract_resource(source: &Self::Source) -> Self { + source.to_owned() + } +} +impl Default for DefaultImageSampler { + fn default() -> Self { + Self::linear() + } } impl Default for Image { diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 851eac1371b9e..a39af9b109368 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -25,6 +25,7 @@ pub use image_texture_loader::*; pub use texture_cache::*; use crate::{ + extract_resource::ExtractResourcePlugin, render_asset::{PrepareAssetLabel, RenderAssetPlugin}, RenderApp, RenderStage, }; @@ -58,6 +59,8 @@ impl Plugin for ImagePlugin { app.add_plugin(RenderAssetPlugin::::with_prepare_asset_label( PrepareAssetLabel::PreAssetPrepare, )) + .init_resource::() + .add_plugin(ExtractResourcePlugin::::default()) .add_asset::(); app.world .resource_mut::>() @@ -65,6 +68,7 @@ impl Plugin for ImagePlugin { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app + .init_resource::() .init_resource::() .add_system_to_stage(RenderStage::Cleanup, update_texture_cache_system); } From 790cfec1dabc80add3e4dc7804200cf81f01ef3d Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 7 Jun 2022 12:29:55 -0700 Subject: [PATCH 6/8] Use derive instead of manual impl --- crates/bevy_render/src/texture/image.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index b9d0fc0ae50d2..b577002c62ddb 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -127,7 +127,7 @@ impl Default for ImageSampler { /// Resource used as the global default image sampler for [`Image`]s with their `sampler_descriptor` /// set to [`ImageSampler::Default`]. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, ExtractResource)] pub struct DefaultImageSampler(pub wgpu::SamplerDescriptor<'static>); impl DefaultImageSampler { pub fn linear() -> Self { @@ -145,13 +145,7 @@ impl DefaultImageSampler { }) } } -impl ExtractResource for DefaultImageSampler { - type Source = DefaultImageSampler; - fn extract_resource(source: &Self::Source) -> Self { - source.to_owned() - } -} impl Default for DefaultImageSampler { fn default() -> Self { Self::linear() From b3352d8b7a6cb3153c93c9c7ccb76e2a7e6fe844 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 7 Jun 2022 21:18:17 -0700 Subject: [PATCH 7/8] Cache default sampler output --- crates/bevy_pbr/src/render/mesh.rs | 18 ++++--- crates/bevy_render/src/texture/image.rs | 66 ++++++++++++++++++------- crates/bevy_sprite/src/mesh2d/mesh.rs | 14 +++--- 3 files changed, 67 insertions(+), 31 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 215ccf5d11a6a..241ce55af881e 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; @@ -277,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, + ResMut, + Res, + )> = SystemState::new(world); + let (render_device, mut 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); @@ -437,13 +442,12 @@ impl FromWorld for MeshPipeline { TextureFormat::bevy_default(), ); let texture = render_device.create_texture(&image.texture_descriptor); - let sampler = render_device.create_sampler(match image.sampler_descriptor { - ImageSampler::Default => &world.resource::().0, - ImageSampler::Descriptor(ref d) => d, - }); + let sampler = match image.sampler_descriptor { + ImageSampler::Default => default_sampler.get_or_create_sampler(&*render_device), + 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/texture/image.rs b/crates/bevy_render/src/texture/image.rs index b577002c62ddb..85afdc6591cd8 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -14,9 +14,13 @@ use crate::{ texture::BevyDefault, }; use bevy_asset::HandleUntyped; -use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; +use bevy_ecs::system::{ + lifetimeless::{SRes, SResMut}, + SystemParamItem, +}; use bevy_math::Vec2; use bevy_reflect::TypeUuid; +use std::hash::Hash; use thiserror::Error; use wgpu::{ Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, TextureDimension, TextureFormat, @@ -128,21 +132,47 @@ impl Default for ImageSampler { /// Resource used as the global default image sampler for [`Image`]s with their `sampler_descriptor` /// set to [`ImageSampler::Default`]. #[derive(Debug, Clone, ExtractResource)] -pub struct DefaultImageSampler(pub wgpu::SamplerDescriptor<'static>); +pub struct DefaultImageSampler { + descriptor: wgpu::SamplerDescriptor<'static>, + cache: Option, +} impl DefaultImageSampler { + /// Get the [`Sampler`] from the cache or compute if empty. + pub fn get_or_create_sampler(&mut self, render_device: &RenderDevice) -> Sampler { + match &self.cache { + Some(sampler) => sampler.to_owned(), + None => { + let sampler = render_device.create_sampler(&self.descriptor); + self.cache = Some(sampler.clone()); + sampler + } + } + } + pub fn new(descriptor: wgpu::SamplerDescriptor<'static>) -> Self { + Self { + descriptor, + cache: None, + } + } pub fn linear() -> Self { - DefaultImageSampler(wgpu::SamplerDescriptor { - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - ..Default::default() - }) + DefaultImageSampler { + descriptor: wgpu::SamplerDescriptor { + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + ..Default::default() + }, + cache: None, + } } pub fn nearest() -> Self { - DefaultImageSampler(wgpu::SamplerDescriptor { - mag_filter: wgpu::FilterMode::Nearest, - min_filter: wgpu::FilterMode::Nearest, - ..Default::default() - }) + DefaultImageSampler { + descriptor: wgpu::SamplerDescriptor { + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }, + cache: None, + } } } @@ -586,7 +616,7 @@ impl RenderAsset for Image { type Param = ( SRes, SRes, - SRes, + SResMut, ); /// Clones the Image. @@ -597,7 +627,7 @@ impl RenderAsset for Image { /// Converts the extracted image into a [`GpuImage`]. fn prepare_asset( image: Self::ExtractedAsset, - (render_device, render_queue, default_sampler): &mut SystemParamItem, + (render_device, render_queue, ref mut default_sampler): &mut SystemParamItem, ) -> Result> { let texture = if image.texture_descriptor.mip_level_count > 1 || image.is_compressed() { render_device.create_texture_with_data( @@ -640,11 +670,11 @@ impl RenderAsset for Image { image.texture_descriptor.size.width as f32, image.texture_descriptor.size.height as f32, ); - let descriptor = match image.sampler_descriptor { - ImageSampler::Default => &default_sampler.0, - ImageSampler::Descriptor(ref d) => d, + let sampler = match image.sampler_descriptor { + ImageSampler::Default => default_sampler.get_or_create_sampler(render_device), + ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor), }; - let sampler = render_device.create_sampler(descriptor); + Ok(GpuImage { texture, texture_view, diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 19a08171b6e2d..c16726c5e57e6 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}; @@ -142,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, ResMut)> = + SystemState::new(world); + let (render_device, mut default_sampler) = system_state.get_mut(world); let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ // View @@ -182,10 +184,10 @@ impl FromWorld for Mesh2dPipeline { TextureFormat::bevy_default(), ); let texture = render_device.create_texture(&image.texture_descriptor); - let sampler = render_device.create_sampler(match image.sampler_descriptor { - ImageSampler::Default => &world.resource::().0, - ImageSampler::Descriptor(ref d) => d, - }); + let sampler = match image.sampler_descriptor { + ImageSampler::Default => default_sampler.get_or_create_sampler(&*render_device), + ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor), + }; let format_size = image.texture_descriptor.format.pixel_size(); let render_queue = world.resource_mut::(); From 265584f644596d65b1559e17af3aea2295d4747f Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 9 Jun 2022 15:59:53 -0700 Subject: [PATCH 8/8] ImageSettings and DefaultImageSampler separation --- crates/bevy_pbr/src/render/mesh.rs | 6 +- crates/bevy_render/src/texture/image.rs | 77 ++++++++----------------- crates/bevy_render/src/texture/mod.rs | 43 ++++++++++++-- crates/bevy_sprite/src/mesh2d/mesh.rs | 6 +- examples/2d/sprite_sheet.rs | 4 +- examples/2d/texture_atlas.rs | 4 +- 6 files changed, 72 insertions(+), 68 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 241ce55af881e..449e711100002 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -279,10 +279,10 @@ impl FromWorld for MeshPipeline { fn from_world(world: &mut World) -> Self { let mut system_state: SystemState<( Res, - ResMut, + Res, Res, )> = SystemState::new(world); - let (render_device, mut default_sampler, render_queue) = system_state.get_mut(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); @@ -443,7 +443,7 @@ impl FromWorld for MeshPipeline { ); let texture = render_device.create_texture(&image.texture_descriptor); let sampler = match image.sampler_descriptor { - ImageSampler::Default => default_sampler.get_or_create_sampler(&*render_device), + ImageSampler::Default => (**default_sampler).clone(), ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor), }; diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index 85afdc6591cd8..f898140acbd73 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -7,17 +7,14 @@ use super::ktx2::*; use super::image_texture_conversion::image_to_texture; use crate::{ - extract_resource::ExtractResource, render_asset::{PrepareAssetError, RenderAsset}, render_resource::{Sampler, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, texture::BevyDefault, }; use bevy_asset::HandleUntyped; -use bevy_ecs::system::{ - lifetimeless::{SRes, SResMut}, - SystemParamItem, -}; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_math::Vec2; use bevy_reflect::TypeUuid; use std::hash::Hash; @@ -129,58 +126,30 @@ impl Default for ImageSampler { } } -/// Resource used as the global default image sampler for [`Image`]s with their `sampler_descriptor` -/// set to [`ImageSampler::Default`]. -#[derive(Debug, Clone, ExtractResource)] -pub struct DefaultImageSampler { - descriptor: wgpu::SamplerDescriptor<'static>, - cache: Option, -} -impl DefaultImageSampler { - /// Get the [`Sampler`] from the cache or compute if empty. - pub fn get_or_create_sampler(&mut self, render_device: &RenderDevice) -> Sampler { - match &self.cache { - Some(sampler) => sampler.to_owned(), - None => { - let sampler = render_device.create_sampler(&self.descriptor); - self.cache = Some(sampler.clone()); - sampler - } - } - } - pub fn new(descriptor: wgpu::SamplerDescriptor<'static>) -> Self { - Self { - descriptor, - cache: None, - } - } - pub fn linear() -> Self { - DefaultImageSampler { - descriptor: wgpu::SamplerDescriptor { - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - ..Default::default() - }, - cache: None, +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() } } - pub fn nearest() -> Self { - DefaultImageSampler { - descriptor: wgpu::SamplerDescriptor { - mag_filter: wgpu::FilterMode::Nearest, - min_filter: wgpu::FilterMode::Nearest, - ..Default::default() - }, - cache: None, + + /// 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() } } } -impl Default for DefaultImageSampler { - fn default() -> Self { - Self::linear() - } -} +/// 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 { @@ -616,7 +585,7 @@ impl RenderAsset for Image { type Param = ( SRes, SRes, - SResMut, + SRes, ); /// Clones the Image. @@ -627,7 +596,7 @@ impl RenderAsset for Image { /// Converts the extracted image into a [`GpuImage`]. fn prepare_asset( image: Self::ExtractedAsset, - (render_device, render_queue, ref mut default_sampler): &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( @@ -671,7 +640,7 @@ impl RenderAsset for Image { image.texture_descriptor.size.height as f32, ); let sampler = match image.sampler_descriptor { - ImageSampler::Default => default_sampler.get_or_create_sampler(render_device), + ImageSampler::Default => (***default_sampler).clone(), ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor), }; diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index a39af9b109368..1ca625d1a2cfc 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -25,8 +25,8 @@ pub use image_texture_loader::*; pub use texture_cache::*; use crate::{ - extract_resource::ExtractResourcePlugin, render_asset::{PrepareAssetLabel, RenderAssetPlugin}, + renderer::RenderDevice, RenderApp, RenderStage, }; use bevy_app::{App, Plugin}; @@ -59,22 +59,57 @@ impl Plugin for ImagePlugin { app.add_plugin(RenderAssetPlugin::::with_prepare_asset_label( PrepareAssetLabel::PreAssetPrepare, )) - .init_resource::() - .add_plugin(ExtractResourcePlugin::::default()) .add_asset::(); app.world .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 - .init_resource::() + .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 c16726c5e57e6..6dc1ce9d0d6b1 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -142,9 +142,9 @@ pub struct Mesh2dPipeline { impl FromWorld for Mesh2dPipeline { fn from_world(world: &mut World) -> Self { - let mut system_state: SystemState<(Res, ResMut)> = + let mut system_state: SystemState<(Res, Res)> = SystemState::new(world); - let (render_device, mut default_sampler) = system_state.get_mut(world); + let (render_device, default_sampler) = system_state.get_mut(world); let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ // View @@ -185,7 +185,7 @@ impl FromWorld for Mesh2dPipeline { ); let texture = render_device.create_texture(&image.texture_descriptor); let sampler = match image.sampler_descriptor { - ImageSampler::Default => default_sampler.get_or_create_sampler(&*render_device), + ImageSampler::Default => (**default_sampler).clone(), ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor), }; diff --git a/examples/2d/sprite_sheet.rs b/examples/2d/sprite_sheet.rs index 54460de7bbae0..4286b88a24737 100644 --- a/examples/2d/sprite_sheet.rs +++ b/examples/2d/sprite_sheet.rs @@ -1,12 +1,12 @@ //! 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::*, render::texture::DefaultImageSampler}; +use bevy::{prelude::*, render::texture::ImageSettings}; fn main() { App::new() + .insert_resource(ImageSettings::default_nearest()) // prevents blurry sprites .add_plugins(DefaultPlugins) - .insert_resource(DefaultImageSampler::nearest()) // prevents blurry sprites .add_startup_system(setup) .add_system(animate_sprite) .run(); diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index f0d95be933929..74599ab9295a5 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -1,13 +1,13 @@ //! In this example we generate a new texture atlas (sprite sheet) from a folder containing //! individual sprites. -use bevy::{asset::LoadState, prelude::*, render::texture::DefaultImageSampler}; +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) - .insert_resource(DefaultImageSampler::nearest()) // prevents blurry sprites .add_state(AppState::Setup) .add_system_set(SystemSet::on_enter(AppState::Setup).with_system(load_textures)) .add_system_set(SystemSet::on_update(AppState::Setup).with_system(check_textures))