Skip to content

Commit 772d152

Browse files
aevyriecart
andcommitted
Change default Image FilterMode to Linear (#4465)
# Objective - Closes #4464 ## Solution - Specify default mag and min filter types for `Image` instead of using `wgpu`'s defaults. --- ## Changelog ### Changed - Default `Image` filtering changed from `Nearest` to `Linear`. Co-authored-by: Carter Anderson <[email protected]>
1 parent 728d969 commit 772d152

File tree

8 files changed

+127
-19
lines changed

8 files changed

+127
-19
lines changed

crates/bevy_gltf/src/loader.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use bevy_render::{
2525
primitives::{Aabb, Frustum},
2626
render_resource::{AddressMode, Face, FilterMode, PrimitiveTopology, SamplerDescriptor},
2727
renderer::RenderDevice,
28-
texture::{CompressedImageFormats, Image, ImageType, TextureError},
28+
texture::{CompressedImageFormats, Image, ImageSampler, ImageType, TextureError},
2929
view::VisibleEntities,
3030
};
3131
use bevy_scene::Scene;
@@ -619,7 +619,7 @@ async fn load_texture<'a>(
619619
)?
620620
}
621621
};
622-
texture.sampler_descriptor = texture_sampler(&gltf_texture);
622+
texture.sampler_descriptor = ImageSampler::Descriptor(texture_sampler(&gltf_texture));
623623

624624
Ok((texture, texture_label(&gltf_texture)))
625625
}

crates/bevy_pbr/src/render/mesh.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use bevy_app::Plugin;
77
use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
88
use bevy_ecs::{
99
prelude::*,
10-
system::{lifetimeless::*, SystemParamItem},
10+
system::{lifetimeless::*, SystemParamItem, SystemState},
1111
};
1212
use bevy_math::{Mat4, Vec2};
1313
use bevy_reflect::TypeUuid;
@@ -21,7 +21,9 @@ use bevy_render::{
2121
render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass},
2222
render_resource::*,
2323
renderer::{RenderDevice, RenderQueue},
24-
texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo},
24+
texture::{
25+
BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
26+
},
2527
view::{ComputedVisibility, ViewUniform, ViewUniformOffset, ViewUniforms},
2628
RenderApp, RenderStage,
2729
};
@@ -275,7 +277,12 @@ pub struct MeshPipeline {
275277

276278
impl FromWorld for MeshPipeline {
277279
fn from_world(world: &mut World) -> Self {
278-
let render_device = world.resource::<RenderDevice>();
280+
let mut system_state: SystemState<(
281+
Res<RenderDevice>,
282+
Res<DefaultImageSampler>,
283+
Res<RenderQueue>,
284+
)> = SystemState::new(world);
285+
let (render_device, default_sampler, render_queue) = system_state.get_mut(world);
279286
let clustered_forward_buffer_binding_type = render_device
280287
.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT);
281288

@@ -435,10 +442,12 @@ impl FromWorld for MeshPipeline {
435442
TextureFormat::bevy_default(),
436443
);
437444
let texture = render_device.create_texture(&image.texture_descriptor);
438-
let sampler = render_device.create_sampler(&image.sampler_descriptor);
445+
let sampler = match image.sampler_descriptor {
446+
ImageSampler::Default => (**default_sampler).clone(),
447+
ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor),
448+
};
439449

440450
let format_size = image.texture_descriptor.format.pixel_size();
441-
let render_queue = world.resource_mut::<RenderQueue>();
442451
render_queue.write_texture(
443452
ImageCopyTexture {
444453
texture: &texture,

crates/bevy_render/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ impl Plugin for RenderPlugin {
180180
.insert_resource(queue)
181181
.insert_resource(adapter_info)
182182
.insert_resource(pipeline_cache)
183-
.insert_resource(asset_server);
183+
.insert_resource(asset_server)
184+
.init_resource::<RenderGraph>();
184185

185186
app.add_sub_app(RenderApp, render_app, move |app_world, render_app| {
186187
#[cfg(feature = "trace")]

crates/bevy_render/src/texture/image.rs

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ use crate::{
1313
texture::BevyDefault,
1414
};
1515
use bevy_asset::HandleUntyped;
16+
use bevy_derive::{Deref, DerefMut};
1617
use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem};
1718
use bevy_math::Vec2;
1819
use bevy_reflect::TypeUuid;
20+
use std::hash::Hash;
1921
use thiserror::Error;
2022
use wgpu::{
2123
Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, TextureDimension, TextureFormat,
@@ -106,9 +108,49 @@ pub struct Image {
106108
pub data: Vec<u8>,
107109
// TODO: this nesting makes accessing Image metadata verbose. Either flatten out descriptor or add accessors
108110
pub texture_descriptor: wgpu::TextureDescriptor<'static>,
109-
pub sampler_descriptor: wgpu::SamplerDescriptor<'static>,
111+
pub sampler_descriptor: ImageSampler,
110112
}
111113

114+
/// Used in `Image`, this determines what image sampler to use when rendering. The default setting,
115+
/// [`ImageSampler::Default`], will result in reading the sampler set in the [`DefaultImageSampler`]
116+
/// resource - the global default sampler - at runtime. Setting this to [`ImageSampler::Descriptor`]
117+
/// will override the global default descriptor for this [`Image`].
118+
#[derive(Debug, Clone)]
119+
pub enum ImageSampler {
120+
Default,
121+
Descriptor(wgpu::SamplerDescriptor<'static>),
122+
}
123+
impl Default for ImageSampler {
124+
fn default() -> Self {
125+
Self::Default
126+
}
127+
}
128+
129+
impl ImageSampler {
130+
/// Returns a sampler descriptor with `Linear` min and mag filters
131+
pub fn linear_descriptor() -> wgpu::SamplerDescriptor<'static> {
132+
wgpu::SamplerDescriptor {
133+
mag_filter: wgpu::FilterMode::Linear,
134+
min_filter: wgpu::FilterMode::Linear,
135+
..Default::default()
136+
}
137+
}
138+
139+
/// Returns a sampler descriptor with `Nearest` min and mag filters
140+
pub fn nearest_descriptor() -> wgpu::SamplerDescriptor<'static> {
141+
wgpu::SamplerDescriptor {
142+
mag_filter: wgpu::FilterMode::Nearest,
143+
min_filter: wgpu::FilterMode::Nearest,
144+
..Default::default()
145+
}
146+
}
147+
}
148+
149+
/// Resource used as the global default image sampler for [`Image`]s with their `sampler_descriptor`
150+
/// set to [`ImageSampler::Default`].
151+
#[derive(Debug, Clone, Deref, DerefMut)]
152+
pub struct DefaultImageSampler(pub(crate) Sampler);
153+
112154
impl Default for Image {
113155
fn default() -> Self {
114156
let format = wgpu::TextureFormat::bevy_default();
@@ -128,7 +170,7 @@ impl Default for Image {
128170
sample_count: 1,
129171
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
130172
},
131-
sampler_descriptor: wgpu::SamplerDescriptor::default(),
173+
sampler_descriptor: ImageSampler::Default,
132174
}
133175
}
134176
}
@@ -540,7 +582,11 @@ pub struct GpuImage {
540582
impl RenderAsset for Image {
541583
type ExtractedAsset = Image;
542584
type PreparedAsset = GpuImage;
543-
type Param = (SRes<RenderDevice>, SRes<RenderQueue>);
585+
type Param = (
586+
SRes<RenderDevice>,
587+
SRes<RenderQueue>,
588+
SRes<DefaultImageSampler>,
589+
);
544590

545591
/// Clones the Image.
546592
fn extract_asset(&self) -> Self::ExtractedAsset {
@@ -550,7 +596,7 @@ impl RenderAsset for Image {
550596
/// Converts the extracted image into a [`GpuImage`].
551597
fn prepare_asset(
552598
image: Self::ExtractedAsset,
553-
(render_device, render_queue): &mut SystemParamItem<Self::Param>,
599+
(render_device, render_queue, default_sampler): &mut SystemParamItem<Self::Param>,
554600
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
555601
let texture = if image.texture_descriptor.mip_level_count > 1 || image.is_compressed() {
556602
render_device.create_texture_with_data(
@@ -593,7 +639,11 @@ impl RenderAsset for Image {
593639
image.texture_descriptor.size.width as f32,
594640
image.texture_descriptor.size.height as f32,
595641
);
596-
let sampler = render_device.create_sampler(&image.sampler_descriptor);
642+
let sampler = match image.sampler_descriptor {
643+
ImageSampler::Default => (***default_sampler).clone(),
644+
ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor),
645+
};
646+
597647
Ok(GpuImage {
598648
texture,
599649
texture_view,

crates/bevy_render/src/texture/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub use texture_cache::*;
2626

2727
use crate::{
2828
render_asset::{PrepareAssetLabel, RenderAssetPlugin},
29+
renderer::RenderDevice,
2930
RenderApp, RenderStage,
3031
};
3132
use bevy_app::{App, Plugin};
@@ -63,14 +64,52 @@ impl Plugin for ImagePlugin {
6364
.resource_mut::<Assets<Image>>()
6465
.set_untracked(DEFAULT_IMAGE_HANDLE, Image::default());
6566

67+
let default_sampler = app
68+
.world
69+
.get_resource_or_insert_with(ImageSettings::default)
70+
.default_sampler
71+
.clone();
6672
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
73+
let default_sampler = {
74+
let device = render_app.world.resource::<RenderDevice>();
75+
device.create_sampler(&default_sampler)
76+
};
6777
render_app
78+
.insert_resource(DefaultImageSampler(default_sampler))
6879
.init_resource::<TextureCache>()
6980
.add_system_to_stage(RenderStage::Cleanup, update_texture_cache_system);
7081
}
7182
}
7283
}
7384

85+
/// [`ImagePlugin`] settings.
86+
pub struct ImageSettings {
87+
/// The default image sampler to use when [`ImageSampler`] is set to `Default`.
88+
pub default_sampler: wgpu::SamplerDescriptor<'static>,
89+
}
90+
91+
impl Default for ImageSettings {
92+
fn default() -> Self {
93+
ImageSettings::default_linear()
94+
}
95+
}
96+
97+
impl ImageSettings {
98+
/// Creates image settings with default linear sampling.
99+
pub fn default_linear() -> ImageSettings {
100+
ImageSettings {
101+
default_sampler: ImageSampler::linear_descriptor(),
102+
}
103+
}
104+
105+
/// Creates image settings with default nearest sampling.
106+
pub fn default_nearest() -> ImageSettings {
107+
ImageSettings {
108+
default_sampler: ImageSampler::nearest_descriptor(),
109+
}
110+
}
111+
}
112+
74113
pub trait BevyDefault {
75114
fn bevy_default() -> Self;
76115
}

crates/bevy_sprite/src/mesh2d/mesh.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use bevy_app::Plugin;
22
use bevy_asset::{load_internal_asset, Handle, HandleUntyped};
33
use bevy_ecs::{
44
prelude::*,
5-
system::{lifetimeless::*, SystemParamItem},
5+
system::{lifetimeless::*, SystemParamItem, SystemState},
66
};
77
use bevy_math::{Mat4, Vec2};
88
use bevy_reflect::{Reflect, TypeUuid};
@@ -13,7 +13,9 @@ use bevy_render::{
1313
render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass},
1414
render_resource::*,
1515
renderer::{RenderDevice, RenderQueue},
16-
texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo},
16+
texture::{
17+
BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
18+
},
1719
view::{ComputedVisibility, ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms},
1820
RenderApp, RenderStage,
1921
};
@@ -140,7 +142,9 @@ pub struct Mesh2dPipeline {
140142

141143
impl FromWorld for Mesh2dPipeline {
142144
fn from_world(world: &mut World) -> Self {
143-
let render_device = world.resource::<RenderDevice>();
145+
let mut system_state: SystemState<(Res<RenderDevice>, Res<DefaultImageSampler>)> =
146+
SystemState::new(world);
147+
let (render_device, default_sampler) = system_state.get_mut(world);
144148
let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
145149
entries: &[
146150
// View
@@ -180,7 +184,10 @@ impl FromWorld for Mesh2dPipeline {
180184
TextureFormat::bevy_default(),
181185
);
182186
let texture = render_device.create_texture(&image.texture_descriptor);
183-
let sampler = render_device.create_sampler(&image.sampler_descriptor);
187+
let sampler = match image.sampler_descriptor {
188+
ImageSampler::Default => (**default_sampler).clone(),
189+
ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor),
190+
};
184191

185192
let format_size = image.texture_descriptor.format.pixel_size();
186193
let render_queue = world.resource_mut::<RenderQueue>();

examples/2d/sprite_sheet.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
//! Renders an animated sprite by loading all animation frames from a single image (a sprite sheet)
22
//! into a texture atlas, and changing the displayed image periodically.
33
4-
use bevy::prelude::*;
4+
use bevy::{prelude::*, render::texture::ImageSettings};
55

66
fn main() {
77
App::new()
8+
.insert_resource(ImageSettings::default_nearest()) // prevents blurry sprites
89
.add_plugins(DefaultPlugins)
910
.add_startup_system(setup)
1011
.add_system(animate_sprite)

examples/2d/texture_atlas.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
//! In this example we generate a new texture atlas (sprite sheet) from a folder containing
22
//! individual sprites.
33
4-
use bevy::{asset::LoadState, prelude::*};
4+
use bevy::{asset::LoadState, prelude::*, render::texture::ImageSettings};
55

66
fn main() {
77
App::new()
88
.init_resource::<RpgSpriteHandles>()
9+
.insert_resource(ImageSettings::default_nearest()) // prevents blurry sprites
910
.add_plugins(DefaultPlugins)
1011
.add_state(AppState::Setup)
1112
.add_system_set(SystemSet::on_enter(AppState::Setup).with_system(load_textures))

0 commit comments

Comments
 (0)