Add the Ability to Specify Egui Texture Filters (#1636)

Only works for egui_glow
This commit is contained in:
Zicklag 2022-05-22 09:56:51 -05:00 committed by GitHub
parent f3e305a646
commit 1a9a0d7ec8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 122 additions and 51 deletions

View File

@ -5,7 +5,7 @@ use crate::{
animation_manager::AnimationManager, data::output::PlatformOutput, frame_state::FrameState, animation_manager::AnimationManager, data::output::PlatformOutput, frame_state::FrameState,
input_state::*, layers::GraphicLayers, memory::Options, output::FullOutput, TextureHandle, *, input_state::*, layers::GraphicLayers, memory::Options, output::FullOutput, TextureHandle, *,
}; };
use epaint::{mutex::*, stats::*, text::Fonts, TessellationOptions, *}; use epaint::{mutex::*, stats::*, text::Fonts, textures::TextureFilter, TessellationOptions, *};
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -19,6 +19,7 @@ impl Default for WrappedTextureManager {
let font_id = tex_mngr.alloc( let font_id = tex_mngr.alloc(
"egui_font_texture".into(), "egui_font_texture".into(),
epaint::FontImage::new([0, 0]).into(), epaint::FontImage::new([0, 0]).into(),
Default::default(),
); );
assert_eq!(font_id, TextureId::default()); assert_eq!(font_id, TextureId::default());
@ -693,7 +694,11 @@ impl Context {
/// fn ui(&mut self, ui: &mut egui::Ui) { /// fn ui(&mut self, ui: &mut egui::Ui) {
/// let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| { /// let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
/// // Load the texture only once. /// // Load the texture only once.
/// ui.ctx().load_texture("my-image", egui::ColorImage::example()) /// ui.ctx().load_texture(
/// "my-image",
/// egui::ColorImage::example(),
/// egui::epaint::textures::TextureFilter::Linear
/// )
/// }); /// });
/// ///
/// // Show the image: /// // Show the image:
@ -707,6 +712,7 @@ impl Context {
&self, &self,
name: impl Into<String>, name: impl Into<String>,
image: impl Into<ImageData>, image: impl Into<ImageData>,
filter: TextureFilter,
) -> TextureHandle { ) -> TextureHandle {
let name = name.into(); let name = name.into();
let image = image.into(); let image = image.into();
@ -720,7 +726,7 @@ impl Context {
max_texture_side max_texture_side
); );
let tex_mngr = self.tex_manager(); let tex_mngr = self.tex_manager();
let tex_id = tex_mngr.write().alloc(name, image); let tex_id = tex_mngr.write().alloc(name, image, filter);
TextureHandle::new(tex_mngr, tex_id) TextureHandle::new(tex_mngr, tex_id)
} }

View File

@ -1526,7 +1526,11 @@ impl Ui {
/// fn ui(&mut self, ui: &mut egui::Ui) { /// fn ui(&mut self, ui: &mut egui::Ui) {
/// let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| { /// let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
/// // Load the texture only once. /// // Load the texture only once.
/// ui.ctx().load_texture("my-image", egui::ColorImage::example()) /// ui.ctx().load_texture(
/// "my-image",
/// egui::ColorImage::example(),
/// egui::epaint::textures::TextureFilter::Linear
/// )
/// }); /// });
/// ///
/// // Show the image: /// // Show the image:

View File

@ -15,7 +15,11 @@ use emath::Rot2;
/// fn ui(&mut self, ui: &mut egui::Ui) { /// fn ui(&mut self, ui: &mut egui::Ui) {
/// let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| { /// let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
/// // Load the texture only once. /// // Load the texture only once.
/// ui.ctx().load_texture("my-image", egui::ColorImage::example()) /// ui.ctx().load_texture(
/// "my-image",
/// egui::ColorImage::example(),
/// egui::epaint::textures::TextureFilter::Linear
/// )
/// }); /// });
/// ///
/// // Show the image: /// // Show the image:

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
use egui::epaint::textures::TextureFilter;
use egui::mutex::Mutex; use egui::mutex::Mutex;
/// An image to be shown in egui. /// An image to be shown in egui.
@ -12,8 +13,8 @@ pub struct RetainedImage {
image: Mutex<egui::ColorImage>, image: Mutex<egui::ColorImage>,
/// Lazily loaded when we have an egui context. /// Lazily loaded when we have an egui context.
texture: Mutex<Option<egui::TextureHandle>>, texture: Mutex<Option<egui::TextureHandle>>,
filter: TextureFilter,
} }
impl RetainedImage { impl RetainedImage {
pub fn from_color_image(debug_name: impl Into<String>, image: ColorImage) -> Self { pub fn from_color_image(debug_name: impl Into<String>, image: ColorImage) -> Self {
Self { Self {
@ -21,9 +22,39 @@ impl RetainedImage {
size: image.size, size: image.size,
image: Mutex::new(image), image: Mutex::new(image),
texture: Default::default(), texture: Default::default(),
filter: Default::default(),
} }
} }
/// Set the texture filter 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.
///
/// # Example
/// ```rust
/// # use egui_extras::RetainedImage;
/// # use egui::{Color32, epaint::{ColorImage, textures::TextureFilter}};
/// # let pixels = vec![Color32::BLACK];
/// # let color_image = ColorImage {
/// # size: [1, 1],
/// # pixels,
/// # };
/// #
/// // 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);
/// ```
pub fn with_texture_filter(mut self, filter: TextureFilter) -> Self {
self.filter = filter;
// If the texture has already been uploaded, this will force it to be re-uploaded with the
// updated filter.
*self.texture.lock() = None;
self
}
/// Load a (non-svg) image. /// Load a (non-svg) image.
/// ///
/// Requires the "image" feature. You must also opt-in to the image formats you need /// Requires the "image" feature. You must also opt-in to the image formats you need
@ -86,7 +117,7 @@ impl RetainedImage {
.get_or_insert_with(|| { .get_or_insert_with(|| {
let image: &mut ColorImage = &mut self.image.lock(); let image: &mut ColorImage = &mut self.image.lock();
let image = std::mem::take(image); let image = std::mem::take(image);
ctx.load_texture(&self.debug_name, image) ctx.load_texture(&self.debug_name, image, self.filter)
}) })
.id() .id()
} }

View File

@ -20,20 +20,13 @@ pub use glow::Context;
const VERT_SRC: &str = include_str!("shader/vertex.glsl"); const VERT_SRC: &str = include_str!("shader/vertex.glsl");
const FRAG_SRC: &str = include_str!("shader/fragment.glsl"); const FRAG_SRC: &str = include_str!("shader/fragment.glsl");
#[derive(Copy, Clone)] pub type TextureFilter = egui::epaint::textures::TextureFilter;
pub enum TextureFilter {
Linear,
Nearest,
}
impl Default for TextureFilter { trait TextureFilterExt {
fn default() -> Self { fn glow_code(&self) -> u32;
TextureFilter::Linear
}
} }
impl TextureFilterExt for TextureFilter {
impl TextureFilter { fn glow_code(&self) -> u32 {
pub(crate) fn glow_code(&self) -> u32 {
match self { match self {
TextureFilter::Linear => glow::LINEAR, TextureFilter::Linear => glow::LINEAR,
TextureFilter::Nearest => glow::NEAREST, TextureFilter::Nearest => glow::NEAREST,
@ -60,8 +53,6 @@ pub struct Painter {
is_embedded: bool, is_embedded: bool,
vao: crate::vao::VertexArrayObject, vao: crate::vao::VertexArrayObject,
srgb_support: bool, srgb_support: bool,
/// The filter used for subsequent textures.
texture_filter: TextureFilter,
post_process: Option<PostProcess>, post_process: Option<PostProcess>,
vbo: glow::Buffer, vbo: glow::Buffer,
element_array_buffer: glow::Buffer, element_array_buffer: glow::Buffer,
@ -216,7 +207,6 @@ impl Painter {
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300), is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
vao, vao,
srgb_support, srgb_support,
texture_filter: Default::default(),
post_process, post_process,
vbo, vbo,
element_array_buffer, element_array_buffer,
@ -457,12 +447,6 @@ impl Painter {
} }
} }
// Set the filter to be used for any subsequent textures loaded via
// [`Self::set_texture`].
pub fn set_texture_filter(&mut self, texture_filter: TextureFilter) {
self.texture_filter = texture_filter;
}
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
pub fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) { pub fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
@ -488,7 +472,7 @@ impl Painter {
let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref()); let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
self.upload_texture_srgb(delta.pos, image.size, data); self.upload_texture_srgb(delta.pos, image.size, delta.filter, data);
} }
egui::ImageData::Font(image) => { egui::ImageData::Font(image) => {
assert_eq!( assert_eq!(
@ -507,12 +491,18 @@ impl Painter {
.flat_map(|a| a.to_array()) .flat_map(|a| a.to_array())
.collect(); .collect();
self.upload_texture_srgb(delta.pos, image.size, &data); self.upload_texture_srgb(delta.pos, image.size, delta.filter, &data);
} }
}; };
} }
fn upload_texture_srgb(&mut self, pos: Option<[usize; 2]>, [w, h]: [usize; 2], data: &[u8]) { fn upload_texture_srgb(
&mut self,
pos: Option<[usize; 2]>,
[w, h]: [usize; 2],
texture_filter: TextureFilter,
data: &[u8],
) {
assert_eq!(data.len(), w * h * 4); assert_eq!(data.len(), w * h * 4);
assert!( assert!(
w >= 1 && h >= 1, w >= 1 && h >= 1,
@ -532,12 +522,12 @@ impl Painter {
self.gl.tex_parameter_i32( self.gl.tex_parameter_i32(
glow::TEXTURE_2D, glow::TEXTURE_2D,
glow::TEXTURE_MAG_FILTER, glow::TEXTURE_MAG_FILTER,
self.texture_filter.glow_code() as i32, texture_filter.glow_code() as i32,
); );
self.gl.tex_parameter_i32( self.gl.tex_parameter_i32(
glow::TEXTURE_2D, glow::TEXTURE_2D,
glow::TEXTURE_MIN_FILTER, glow::TEXTURE_MIN_FILTER,
self.texture_filter.glow_code() as i32, texture_filter.glow_code() as i32,
); );
self.gl.tex_parameter_i32( self.gl.tex_parameter_i32(

View File

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

View File

@ -1,6 +1,6 @@
use emath::{remap_clamp, Rect}; use emath::{remap_clamp, Rect};
use crate::{FontImage, ImageDelta}; use crate::{textures::TextureFilter, FontImage, ImageDelta};
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct Rectu { struct Rectu {
@ -176,12 +176,12 @@ impl TextureAtlas {
if dirty == Rectu::NOTHING { if dirty == Rectu::NOTHING {
None None
} else if dirty == Rectu::EVERYTHING { } else if dirty == Rectu::EVERYTHING {
Some(ImageDelta::full(self.image.clone())) Some(ImageDelta::full(self.image.clone(), TextureFilter::Linear))
} else { } else {
let pos = [dirty.min_x, dirty.min_y]; let pos = [dirty.min_x, dirty.min_y];
let size = [dirty.max_x - dirty.min_x, dirty.max_y - dirty.min_y]; let size = [dirty.max_x - dirty.min_x, dirty.max_y - dirty.min_y];
let region = self.image.region(pos, size); let region = self.image.region(pos, size);
Some(ImageDelta::partial(pos, region)) Some(ImageDelta::partial(pos, region, TextureFilter::Linear))
} }
} }

View File

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

View File

@ -27,7 +27,7 @@ impl TextureManager {
/// MUST have a white pixel at (0,0) ([`crate::WHITE_UV`]). /// 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. /// 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) -> TextureId { pub fn alloc(&mut self, name: String, image: ImageData, filter: TextureFilter) -> TextureId {
let id = TextureId::Managed(self.next_id); let id = TextureId::Managed(self.next_id);
self.next_id += 1; self.next_id += 1;
@ -36,9 +36,10 @@ impl TextureManager {
size: image.size(), size: image.size(),
bytes_per_pixel: image.bytes_per_pixel(), bytes_per_pixel: image.bytes_per_pixel(),
retain_count: 1, retain_count: 1,
filter,
}); });
self.delta.set.insert(id, ImageDelta::full(image)); self.delta.set.insert(id, ImageDelta::full(image, filter));
id id
} }
@ -130,6 +131,22 @@ pub struct TextureMeta {
/// Free when this reaches zero. /// Free when this reaches zero.
pub retain_count: usize, pub retain_count: usize,
/// The texture filtering mode to use when rendering
pub filter: TextureFilter,
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum TextureFilter {
Linear,
Nearest,
}
impl Default for TextureFilter {
fn default() -> Self {
Self::Linear
}
} }
impl TextureMeta { impl TextureMeta {