Browse Source

Specify deifferent minification and magnification filters (#2224)

* Specify deifferent minification and magnification filters

* Fixes

* Update changelogs

* Doctest fixes

* Add deprecation notice for RetainedImage::with_texture_filter
pull/2233/head
Emil Ernerfeldt 2 years ago
committed by GitHub
parent
commit
34e6e12f00
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 23
      crates/egui-wgpu/src/renderer.rs
  3. 8
      crates/egui/src/context.rs
  4. 2
      crates/egui/src/lib.rs
  5. 2
      crates/egui/src/ui.rs
  6. 2
      crates/egui/src/widgets/image.rs
  7. 4
      crates/egui_demo_lib/src/color_test.rs
  8. 7
      crates/egui_demo_lib/src/demo/plot_demo.rs
  9. 7
      crates/egui_demo_lib/src/demo/widget_gallery.rs
  10. 27
      crates/egui_extras/src/image.rs
  11. 2
      crates/egui_glium/examples/native_texture.rs
  12. 40
      crates/egui_glium/src/painter.rs
  13. 18
      crates/egui_glow/src/painter.rs
  14. 1
      crates/epaint/CHANGELOG.md
  15. 12
      crates/epaint/src/image.rs
  16. 8
      crates/epaint/src/texture_atlas.rs
  17. 10
      crates/epaint/src/texture_handle.rs
  18. 68
      crates/epaint/src/textures.rs
  19. 2
      examples/screenshot/src/main.rs

1
CHANGELOG.md

@ -12,6 +12,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
* Added `Context::os/Context::set_os` to query/set what operating system egui believes it is running on ([#2202](https://github.com/emilk/egui/pull/2202)).
* Added `Button::shortcut_text` for showing keyboard shortcuts in menu buttons ([#2202](https://github.com/emilk/egui/pull/2202)).
* Added `egui::KeyboardShortcut` for showing keyboard shortcuts in menu buttons ([#2202](https://github.com/emilk/egui/pull/2202)).
* Texture loading now takes a `TexureOptions` with minification and magnification filters ([#2224](https://github.com/emilk/egui/pull/2224)).
### Fixed 🐛
* ⚠️ BREAKING: Fix text being too small ([#2069](https://github.com/emilk/egui/pull/2069)).

23
crates/egui-wgpu/src/renderer.rs

@ -136,7 +136,7 @@ pub struct Renderer {
/// sampler.
textures: HashMap<egui::TextureId, (Option<wgpu::Texture>, wgpu::BindGroup)>,
next_user_texture_id: u64,
samplers: HashMap<egui::TextureFilter, wgpu::Sampler>,
samplers: HashMap<egui::TextureOptions, wgpu::Sampler>,
/// Storage for use by [`egui::PaintCallback`]'s that need to store resources such as render
/// pipelines that must have the lifetime of the renderpass.
@ -530,8 +530,8 @@ impl Renderer {
});
let sampler = self
.samplers
.entry(image_delta.filter)
.or_insert_with(|| create_sampler(image_delta.filter, device));
.entry(image_delta.options)
.or_insert_with(|| create_sampler(image_delta.options, device));
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label,
layout: &self.texture_bind_group_layout,
@ -790,15 +790,22 @@ impl Renderer {
}
}
fn create_sampler(filter: egui::TextureFilter, device: &wgpu::Device) -> wgpu::Sampler {
let wgpu_filter = match filter {
fn create_sampler(options: egui::TextureOptions, device: &wgpu::Device) -> wgpu::Sampler {
let mag_filter = match options.magnification {
egui::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
egui::TextureFilter::Linear => wgpu::FilterMode::Linear,
};
let min_filter = match options.minification {
egui::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
egui::TextureFilter::Linear => wgpu::FilterMode::Linear,
};
device.create_sampler(&wgpu::SamplerDescriptor {
label: Some(&format!("egui sampler ({:?})", filter)),
mag_filter: wgpu_filter,
min_filter: wgpu_filter,
label: Some(&format!(
"egui sampler (mag: {:?}, min {:?})",
mag_filter, min_filter
)),
mag_filter,
min_filter,
..Default::default()
})
}

8
crates/egui/src/context.rs

@ -6,7 +6,7 @@ use crate::{
input_state::*, layers::GraphicLayers, memory::Options, os::OperatingSystem,
output::FullOutput, TextureHandle, *,
};
use epaint::{mutex::*, stats::*, text::Fonts, textures::TextureFilter, TessellationOptions, *};
use epaint::{mutex::*, stats::*, text::Fonts, TessellationOptions, *};
// ----------------------------------------------------------------------------
@ -800,7 +800,7 @@ impl Context {
/// ui.ctx().load_texture(
/// "my-image",
/// egui::ColorImage::example(),
/// egui::TextureFilter::Linear
/// Default::default()
/// )
/// });
///
@ -815,7 +815,7 @@ impl Context {
&self,
name: impl Into<String>,
image: impl Into<ImageData>,
filter: TextureFilter,
options: TextureOptions,
) -> TextureHandle {
let name = name.into();
let image = image.into();
@ -829,7 +829,7 @@ impl Context {
max_texture_side
);
let tex_mngr = self.tex_manager();
let tex_id = tex_mngr.write().alloc(name, image, filter);
let tex_id = tex_mngr.write().alloc(name, image, options);
TextureHandle::new(tex_mngr, tex_id)
}

2
crates/egui/src/lib.rs

@ -332,7 +332,7 @@ pub use epaint::hex_color;
pub use epaint::{
color, mutex,
text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak},
textures::{TextureFilter, TexturesDelta},
textures::{TextureFilter, TextureOptions, TexturesDelta},
ClippedPrimitive, Color32, ColorImage, FontImage, ImageData, Mesh, PaintCallback,
PaintCallbackInfo, Rgba, Rounding, Shape, Stroke, TextureHandle, TextureId,
};

2
crates/egui/src/ui.rs

@ -1553,7 +1553,7 @@ impl Ui {
/// ui.ctx().load_texture(
/// "my-image",
/// egui::ColorImage::example(),
/// egui::TextureFilter::Linear
/// Default::default()
/// )
/// });
///

2
crates/egui/src/widgets/image.rs

@ -18,7 +18,7 @@ use emath::Rot2;
/// ui.ctx().load_texture(
/// "my-image",
/// egui::ColorImage::example(),
/// egui::TextureFilter::Linear
/// Default::default()
/// )
/// });
///

4
crates/egui_demo_lib/src/color_test.rs

@ -1,6 +1,6 @@
use std::collections::HashMap;
use egui::{color::*, widgets::color_picker::show_color, TextureFilter, *};
use egui::{color::*, widgets::color_picker::show_color, TextureOptions, *};
const GRADIENT_SIZE: Vec2 = vec2(256.0, 18.0);
@ -372,7 +372,7 @@ impl TextureManager {
size: [width, height],
pixels,
},
TextureFilter::Linear,
TextureOptions::LINEAR,
)
})
}

7
crates/egui_demo_lib/src/demo/plot_demo.rs

@ -596,11 +596,8 @@ impl ItemsDemo {
};
let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
ui.ctx().load_texture(
"plot_demo",
egui::ColorImage::example(),
egui::TextureFilter::Linear,
)
ui.ctx()
.load_texture("plot_demo", egui::ColorImage::example(), Default::default())
});
let image = PlotImage::new(
texture,

7
crates/egui_demo_lib/src/demo/widget_gallery.rs

@ -115,11 +115,8 @@ impl WidgetGallery {
} = self;
let texture: &egui::TextureHandle = texture.get_or_insert_with(|| {
ui.ctx().load_texture(
"example",
egui::ColorImage::example(),
egui::TextureFilter::Linear,
)
ui.ctx()
.load_texture("example", egui::ColorImage::example(), Default::default())
});
ui.add(doc_link_label("Label", "label,heading"));

27
crates/egui_extras/src/image.rs

@ -1,5 +1,4 @@
use egui::mutex::Mutex;
use egui::TextureFilter;
use egui::{mutex::Mutex, TextureFilter, TextureOptions};
/// An image to be shown in egui.
///
@ -13,7 +12,7 @@ pub struct RetainedImage {
image: Mutex<egui::ColorImage>,
/// Lazily loaded when we have an egui context.
texture: Mutex<Option<egui::TextureHandle>>,
filter: TextureFilter,
options: TextureOptions,
}
impl RetainedImage {
@ -23,7 +22,7 @@ impl RetainedImage {
size: image.size,
image: Mutex::new(image),
texture: Default::default(),
filter: Default::default(),
options: Default::default(),
}
}
@ -68,7 +67,7 @@ impl RetainedImage {
Self::from_svg_bytes(debug_name, svg_str.as_bytes())
}
/// Set the texture filter to use for the image.
/// Set the texture filters to use for the image.
///
/// **Note:** If the texture has already been uploaded to the GPU, this will require
/// re-uploading the texture with the updated filter.
@ -76,7 +75,7 @@ impl RetainedImage {
/// # Example
/// ```rust
/// # use egui_extras::RetainedImage;
/// # use egui::{Color32, epaint::{ColorImage, textures::TextureFilter}};
/// # use egui::{Color32, epaint::{ColorImage, textures::TextureOptions}};
/// # let pixels = vec![Color32::BLACK];
/// # let color_image = ColorImage {
/// # size: [1, 1],
@ -85,10 +84,10 @@ impl RetainedImage {
/// #
/// // Upload a pixel art image without it getting blurry when resized
/// let image = RetainedImage::from_color_image("my_image", color_image)
/// .with_texture_filter(TextureFilter::Nearest);
/// .with_options(TextureOptions::NEAREST);
/// ```
pub fn with_texture_filter(mut self, filter: TextureFilter) -> Self {
self.filter = filter;
pub fn with_options(mut self, options: TextureOptions) -> Self {
self.options = options;
// If the texture has already been uploaded, this will force it to be re-uploaded with the
// updated filter.
@ -97,6 +96,14 @@ impl RetainedImage {
self
}
#[deprecated = "Use with_options instead"]
pub fn with_texture_filter(self, filter: TextureFilter) -> Self {
self.with_options(TextureOptions {
magnification: filter,
minification: filter,
})
}
/// The size of the image data (number of pixels wide/high).
pub fn size(&self) -> [usize; 2] {
self.size
@ -130,7 +137,7 @@ impl RetainedImage {
.get_or_insert_with(|| {
let image: &mut ColorImage = &mut self.image.lock();
let image = std::mem::take(image);
ctx.load_texture(&self.debug_name, image, self.filter)
ctx.load_texture(&self.debug_name, image, self.options)
})
.id()
}

2
crates/egui_glium/examples/native_texture.rs

@ -18,7 +18,7 @@ fn main() {
// Allocate egui's texture id for GL texture
let texture_id = egui_glium
.painter
.register_native_texture(glium_texture, egui::TextureFilter::Linear);
.register_native_texture(glium_texture, Default::default());
// Setup button image size for reasonable image size for button container.
let button_image_size = egui::vec2(32_f32, 32_f32);

40
crates/egui_glium/src/painter.rs

@ -1,7 +1,10 @@
#![allow(deprecated)] // legacy implement_vertex macro
#![allow(semicolon_in_expressions_from_macros)] // glium::program! macro
use egui::{epaint::Primitive, TextureFilter};
use egui::{
epaint::{textures::TextureFilter, Primitive},
TextureOptions,
};
use {
egui::{emath::Rect, epaint::Mesh},
@ -10,7 +13,7 @@ use {
index::PrimitiveType,
texture::{self, srgb_texture2d::SrgbTexture2d},
uniform,
uniforms::{MagnifySamplerFilter, SamplerWrapFunction},
uniforms::{MagnifySamplerFilter, MinifySamplerFilter, SamplerWrapFunction},
},
std::rc::Rc,
};
@ -191,14 +194,25 @@ impl Painter {
if let Some(texture) = self.texture(mesh.texture_id) {
// The texture coordinates for text are so that both nearest and linear should work with the egui font texture.
let filter = match texture.filter {
let mag_filter = match texture.options.magnification {
TextureFilter::Nearest => MagnifySamplerFilter::Nearest,
TextureFilter::Linear => MagnifySamplerFilter::Linear,
};
let min_filter = match texture.options.minification {
TextureFilter::Nearest => MinifySamplerFilter::Nearest,
TextureFilter::Linear => MinifySamplerFilter::Linear,
};
let sampler = texture
.glium_texture
.sampled()
.magnify_filter(mag_filter)
.minify_filter(min_filter)
.wrap_function(SamplerWrapFunction::Clamp);
let uniforms = uniform! {
u_screen_size: [width_in_points, height_in_points],
u_sampler: texture.glium_texture.sampled().magnify_filter(filter).wrap_function(SamplerWrapFunction::Clamp),
u_sampler: sampler,
};
// egui outputs colors with premultiplied alpha:
@ -309,13 +323,13 @@ impl Painter {
.main_level()
.write(rect, glium_image);
user_texture.filter = delta.filter;
user_texture.options = delta.options;
}
} else {
let gl_texture =
SrgbTexture2d::with_format(facade, glium_image, format, mipmaps).unwrap();
let user_texture = EguiTexture::new(gl_texture.into(), delta.filter);
let user_texture = EguiTexture::new(gl_texture.into(), delta.options);
self.textures.insert(tex_id, user_texture);
}
}
@ -331,12 +345,12 @@ impl Painter {
pub fn register_native_texture(
&mut self,
native: Rc<SrgbTexture2d>,
filter: TextureFilter,
options: TextureOptions,
) -> egui::TextureId {
let id = egui::TextureId::User(self.next_native_tex_id);
self.next_native_tex_id += 1;
let texture = EguiTexture::new(native, filter);
let texture = EguiTexture::new(native, options);
self.textures.insert(id, texture);
id
}
@ -345,23 +359,23 @@ impl Painter {
&mut self,
id: egui::TextureId,
replacing: Rc<SrgbTexture2d>,
filter: TextureFilter,
options: TextureOptions,
) {
let texture = EguiTexture::new(replacing, filter);
let texture = EguiTexture::new(replacing, options);
self.textures.insert(id, texture);
}
}
struct EguiTexture {
glium_texture: Rc<SrgbTexture2d>,
filter: TextureFilter,
options: TextureOptions,
}
impl EguiTexture {
fn new(glium_texture: Rc<SrgbTexture2d>, filter: TextureFilter) -> Self {
fn new(glium_texture: Rc<SrgbTexture2d>, options: TextureOptions) -> Self {
Self {
glium_texture,
filter,
options,
}
}
}

18
crates/egui_glow/src/painter.rs

@ -20,17 +20,15 @@ pub use glow::Context;
const VERT_SRC: &str = include_str!("shader/vertex.glsl");
const FRAG_SRC: &str = include_str!("shader/fragment.glsl");
pub type TextureFilter = egui::TextureFilter;
trait TextureFilterExt {
fn glow_code(&self) -> u32;
}
impl TextureFilterExt for TextureFilter {
impl TextureFilterExt for egui::TextureFilter {
fn glow_code(&self) -> u32 {
match self {
TextureFilter::Linear => glow::LINEAR,
TextureFilter::Nearest => glow::NEAREST,
egui::TextureFilter::Linear => glow::LINEAR,
egui::TextureFilter::Nearest => glow::NEAREST,
}
}
}
@ -469,7 +467,7 @@ impl Painter {
let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
self.upload_texture_srgb(delta.pos, image.size, delta.filter, data);
self.upload_texture_srgb(delta.pos, image.size, delta.options, data);
}
egui::ImageData::Font(image) => {
assert_eq!(
@ -483,7 +481,7 @@ impl Painter {
.flat_map(|a| a.to_array())
.collect();
self.upload_texture_srgb(delta.pos, image.size, delta.filter, &data);
self.upload_texture_srgb(delta.pos, image.size, delta.options, &data);
}
};
}
@ -492,7 +490,7 @@ impl Painter {
&mut self,
pos: Option<[usize; 2]>,
[w, h]: [usize; 2],
texture_filter: TextureFilter,
options: egui::TextureOptions,
data: &[u8],
) {
assert_eq!(data.len(), w * h * 4);
@ -508,12 +506,12 @@ impl Painter {
self.gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_MAG_FILTER,
texture_filter.glow_code() as i32,
options.magnification.glow_code() as i32,
);
self.gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_MIN_FILTER,
texture_filter.glow_code() as i32,
options.minification.glow_code() as i32,
);
self.gl.tex_parameter_i32(

1
crates/epaint/CHANGELOG.md

@ -13,6 +13,7 @@ All notable changes to the epaint crate will be documented in this file.
* Added `epaint::hex_color!` to create `Color32`'s from hex strings under the `color-hex` feature ([#1596](https://github.com/emilk/egui/pull/1596)).
* Optimize tessellation of filled circles by 10x or more ([#1616](https://github.com/emilk/egui/pull/1616)).
* Added opt-in feature `deadlock_detection` to detect double-lock of mutexes on the same thread ([#1619](https://github.com/emilk/egui/pull/1619)).
* Texture loading now takes a `TexureOptions` with minification and magnification filters ([#2224](https://github.com/emilk/egui/pull/2224)).
## 0.18.1 - 2022-05-01

12
crates/epaint/src/image.rs

@ -1,4 +1,4 @@
use crate::{textures::TextureFilter, Color32};
use crate::{textures::TextureOptions, Color32};
/// An image stored in RAM.
///
@ -291,7 +291,7 @@ pub struct ImageDelta {
/// If [`Self::pos`] is `Some`, this describes a patch of the whole image starting at [`Self::pos`].
pub image: ImageData,
pub filter: TextureFilter,
pub options: TextureOptions,
/// If `None`, set the whole texture to [`Self::image`].
///
@ -301,19 +301,19 @@ pub struct ImageDelta {
impl ImageDelta {
/// Update the whole texture.
pub fn full(image: impl Into<ImageData>, filter: TextureFilter) -> Self {
pub fn full(image: impl Into<ImageData>, options: TextureOptions) -> Self {
Self {
image: image.into(),
filter,
options,
pos: None,
}
}
/// Update a sub-region of an existing texture.
pub fn partial(pos: [usize; 2], image: impl Into<ImageData>, filter: TextureFilter) -> Self {
pub fn partial(pos: [usize; 2], image: impl Into<ImageData>, options: TextureOptions) -> Self {
Self {
image: image.into(),
filter,
options,
pos: Some(pos),
}
}

8
crates/epaint/src/texture_atlas.rs

@ -1,6 +1,6 @@
use emath::{remap_clamp, Rect};
use crate::{textures::TextureFilter, FontImage, ImageDelta};
use crate::{FontImage, ImageDelta};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct Rectu {
@ -174,16 +174,18 @@ impl TextureAtlas {
/// Call to get the change to the image since last call.
pub fn take_delta(&mut self) -> Option<ImageDelta> {
let texture_options = crate::textures::TextureOptions::LINEAR;
let dirty = std::mem::replace(&mut self.dirty, Rectu::NOTHING);
if dirty == Rectu::NOTHING {
None
} else if dirty == Rectu::EVERYTHING {
Some(ImageDelta::full(self.image.clone(), TextureFilter::Linear))
Some(ImageDelta::full(self.image.clone(), texture_options))
} else {
let pos = [dirty.min_x, dirty.min_y];
let size = [dirty.max_x - dirty.min_x, dirty.max_y - dirty.min_y];
let region = self.image.region(pos, size);
Some(ImageDelta::partial(pos, region, TextureFilter::Linear))
Some(ImageDelta::partial(pos, region, texture_options))
}
}

10
crates/epaint/src/texture_handle.rs

@ -1,7 +1,7 @@
use std::sync::Arc;
use crate::{
emath::NumExt, mutex::RwLock, textures::TextureFilter, ImageData, ImageDelta, TextureId,
emath::NumExt, mutex::RwLock, textures::TextureOptions, ImageData, ImageDelta, TextureId,
TextureManager,
};
@ -66,10 +66,10 @@ impl TextureHandle {
}
/// Assign a new image to an existing texture.
pub fn set(&mut self, image: impl Into<ImageData>, filter: TextureFilter) {
pub fn set(&mut self, image: impl Into<ImageData>, options: TextureOptions) {
self.tex_mngr
.write()
.set(self.id, ImageDelta::full(image.into(), filter));
.set(self.id, ImageDelta::full(image.into(), options));
}
/// Assign a new image to a subregion of the whole texture.
@ -77,11 +77,11 @@ impl TextureHandle {
&mut self,
pos: [usize; 2],
image: impl Into<ImageData>,
filter: TextureFilter,
options: TextureOptions,
) {
self.tex_mngr
.write()
.set(self.id, ImageDelta::partial(pos, image.into(), filter));
.set(self.id, ImageDelta::partial(pos, image.into(), options));
}
/// width x height

68
crates/epaint/src/textures.rs

@ -26,7 +26,7 @@ impl TextureManager {
/// MUST have a white pixel at (0,0) ([`crate::WHITE_UV`]).
///
/// The texture is given a retain-count of `1`, requiring one call to [`Self::free`] to free it.
pub fn alloc(&mut self, name: String, image: ImageData, filter: TextureFilter) -> TextureId {
pub fn alloc(&mut self, name: String, image: ImageData, options: TextureOptions) -> TextureId {
let id = TextureId::Managed(self.next_id);
self.next_id += 1;
@ -35,10 +35,10 @@ impl TextureManager {
size: image.size(),
bytes_per_pixel: image.bytes_per_pixel(),
retain_count: 1,
filter,
options,
});
self.delta.set.push((id, ImageDelta::full(image, filter)));
self.delta.set.push((id, ImageDelta::full(image, options)));
id
}
@ -131,8 +131,50 @@ pub struct TextureMeta {
/// Free when this reaches zero.
pub retain_count: usize,
/// The texture filtering mode to use when rendering
pub filter: TextureFilter,
/// The texture filtering mode to use when rendering.
pub options: TextureOptions,
}
impl TextureMeta {
/// Size in bytes.
/// width x height x [`Self::bytes_per_pixel`].
pub fn bytes_used(&self) -> usize {
self.size[0] * self.size[1] * self.bytes_per_pixel
}
}
// ----------------------------------------------------------------------------
/// How the texture texels are filtered.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct TextureOptions {
/// How to filter when magnifying (when texels are larger than pixels).
pub magnification: TextureFilter,
/// How to filter when minifying (when texels are smaller than pixels).
pub minification: TextureFilter,
}
impl TextureOptions {
/// Linear magnification and minification.
pub const LINEAR: Self = Self {
magnification: TextureFilter::Linear,
minification: TextureFilter::Linear,
};
/// Nearest magnification and minification.
pub const NEAREST: Self = Self {
magnification: TextureFilter::Nearest,
minification: TextureFilter::Nearest,
};
}
impl Default for TextureOptions {
/// The default is linear for both magnification and minification.
fn default() -> Self {
Self::LINEAR
}
}
/// How the texture texels are filtered.
@ -146,25 +188,9 @@ pub enum TextureFilter {
Nearest,
/// Linearly interpolate the nearest neighbors, creating a smoother look when zooming in and out.
///
/// This is the default.
Linear,
}
impl Default for TextureFilter {
fn default() -> Self {
Self::Linear
}
}
impl TextureMeta {
/// Size in bytes.
/// width x height x [`Self::bytes_per_pixel`].
pub fn bytes_used(&self) -> usize {
self.size[0] * self.size[1] * self.bytes_per_pixel
}
}
// ----------------------------------------------------------------------------
/// What has been allocated and freed during the last period.

2
examples/screenshot/src/main.rs

@ -30,7 +30,7 @@ impl eframe::App for MyApp {
self.texture = Some(ui.ctx().load_texture(
"screenshot",
screenshot,
egui::TextureFilter::Linear,
Default::default(),
));
}

Loading…
Cancel
Save