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 <mcanders1@gmail.com>
This commit is contained in:
Aevyrie 2022-06-11 09:13:37 +00:00
parent 728d9696d7
commit 772d15238c
8 changed files with 127 additions and 19 deletions

View file

@ -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)))
}

View file

@ -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::<RenderDevice>();
let mut system_state: SystemState<(
Res<RenderDevice>,
Res<DefaultImageSampler>,
Res<RenderQueue>,
)> = 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::<RenderQueue>();
render_queue.write_texture(
ImageCopyTexture {
texture: &texture,

View file

@ -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::<RenderGraph>();
app.add_sub_app(RenderApp, render_app, move |app_world, render_app| {
#[cfg(feature = "trace")]

View file

@ -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<u8>,
// 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<RenderDevice>, SRes<RenderQueue>);
type Param = (
SRes<RenderDevice>,
SRes<RenderQueue>,
SRes<DefaultImageSampler>,
);
/// 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<Self::Param>,
(render_device, render_queue, default_sampler): &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
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,

View file

@ -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::<Assets<Image>>()
.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::<RenderDevice>();
device.create_sampler(&default_sampler)
};
render_app
.insert_resource(DefaultImageSampler(default_sampler))
.init_resource::<TextureCache>()
.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;
}

View file

@ -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::<RenderDevice>();
let mut system_state: SystemState<(Res<RenderDevice>, Res<DefaultImageSampler>)> =
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::<RenderQueue>();

View file

@ -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)

View file

@ -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::<RpgSpriteHandles>()
.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))