Skip to content

Commit

Permalink
Add the Ability to Specify Egui Texture Filters
Browse files Browse the repository at this point in the history
Only works for supporting backends:

- egui_glow
  • Loading branch information
zicklag committed May 21, 2022
1 parent f807a29 commit b4df5e7
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 48 deletions.
6 changes: 4 additions & 2 deletions egui/src/context.rs
Expand Up @@ -5,7 +5,7 @@ use crate::{
animation_manager::AnimationManager, data::output::PlatformOutput, frame_state::FrameState,
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, *};

// ----------------------------------------------------------------------------

Expand All @@ -19,6 +19,7 @@ impl Default for WrappedTextureManager {
let font_id = tex_mngr.alloc(
"egui_font_texture".into(),
epaint::FontImage::new([0, 0]).into(),
Default::default(),
);
assert_eq!(font_id, TextureId::default());

Expand Down Expand Up @@ -707,6 +708,7 @@ impl Context {
&self,
name: impl Into<String>,
image: impl Into<ImageData>,
filter: TextureFilter,
) -> TextureHandle {
let name = name.into();
let image = image.into();
Expand All @@ -720,7 +722,7 @@ impl Context {
max_texture_side
);
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)
}

Expand Down
3 changes: 2 additions & 1 deletion egui_demo_lib/src/color_test.rs
@@ -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;

const GRADIENT_SIZE: Vec2 = vec2(256.0, 24.0);
Expand Down Expand Up @@ -334,6 +334,7 @@ impl TextureManager {
size: [width, height],
pixels,
},
TextureFilter::Linear,
)
})
}
Expand Down
7 changes: 5 additions & 2 deletions egui_demo_lib/src/demo/plot_demo.rs
Expand Up @@ -549,8 +549,11 @@ impl ItemsDemo {
};

let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
ui.ctx()
.load_texture("plot_demo", egui::ColorImage::example())
ui.ctx().load_texture(
"plot_demo",
egui::ColorImage::example(),
egui::epaint::textures::TextureFilter::Linear,
)
});
let image = PlotImage::new(
texture,
Expand Down
7 changes: 5 additions & 2 deletions egui_demo_lib/src/demo/widget_gallery.rs
Expand Up @@ -115,8 +115,11 @@ impl WidgetGallery {
} = self;

let texture: &egui::TextureHandle = texture.get_or_insert_with(|| {
ui.ctx()
.load_texture("example", egui::ColorImage::example())
ui.ctx().load_texture(
"example",
egui::ColorImage::example(),
egui::epaint::textures::TextureFilter::Linear,
)
});

ui.add(doc_link_label("Label", "label,heading"));
Expand Down
35 changes: 33 additions & 2 deletions egui_extras/src/image.rs
@@ -1,3 +1,4 @@
use egui::epaint::textures::TextureFilter;
use egui::mutex::Mutex;

/// An image to be shown in egui.
Expand All @@ -12,18 +13,48 @@ pub struct RetainedImage {
image: Mutex<egui::ColorImage>,
/// Lazily loaded when we have an egui context.
texture: Mutex<Option<egui::TextureHandle>>,
filter: TextureFilter,
}

impl RetainedImage {
pub fn from_color_image(debug_name: impl Into<String>, image: ColorImage) -> Self {
Self {
debug_name: debug_name.into(),
size: image.size,
image: Mutex::new(image),
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.
///
/// Requires the "image" feature. You must also opt-in to the image formats you need
Expand Down Expand Up @@ -86,7 +117,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)
ctx.load_texture(&self.debug_name, image, self.filter)
})
.id()
}
Expand Down
42 changes: 16 additions & 26 deletions egui_glow/src/painter.rs
Expand Up @@ -20,20 +20,13 @@ pub use glow::Context;
const VERT_SRC: &str = include_str!("shader/vertex.glsl");
const FRAG_SRC: &str = include_str!("shader/fragment.glsl");

#[derive(Copy, Clone)]
pub enum TextureFilter {
Linear,
Nearest,
}
pub type TextureFilter = egui::epaint::textures::TextureFilter;

impl Default for TextureFilter {
fn default() -> Self {
TextureFilter::Linear
}
trait TextureFilterExt {
fn glow_code(&self) -> u32;
}

impl TextureFilter {
pub(crate) fn glow_code(&self) -> u32 {
impl TextureFilterExt for TextureFilter {
fn glow_code(&self) -> u32 {
match self {
TextureFilter::Linear => glow::LINEAR,
TextureFilter::Nearest => glow::NEAREST,
Expand All @@ -60,8 +53,6 @@ pub struct Painter {
is_embedded: bool,
vao: crate::vao::VertexArrayObject,
srgb_support: bool,
/// The filter used for subsequent textures.
texture_filter: TextureFilter,
post_process: Option<PostProcess>,
vbo: glow::Buffer,
element_array_buffer: glow::Buffer,
Expand Down Expand Up @@ -216,7 +207,6 @@ impl Painter {
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
vao,
srgb_support,
texture_filter: Default::default(),
post_process,
vbo,
element_array_buffer,
Expand Down Expand Up @@ -450,12 +440,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) {
Expand All @@ -481,7 +465,7 @@ impl Painter {

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.into(), data);
}
egui::ImageData::Font(image) => {
assert_eq!(
Expand All @@ -500,12 +484,18 @@ impl Painter {
.flat_map(|a| a.to_array())
.collect();

self.upload_texture_srgb(delta.pos, image.size, &data);
self.upload_texture_srgb(delta.pos, image.size, delta.filter.into(), &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!(
w >= 1 && h >= 1,
Expand All @@ -525,12 +515,12 @@ impl Painter {
self.gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_MAG_FILTER,
self.texture_filter.glow_code() as i32,
texture_filter.glow_code() as i32,
);
self.gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_MIN_FILTER,
self.texture_filter.glow_code() as i32,
texture_filter.glow_code() as i32,
);

self.gl.tex_parameter_i32(
Expand Down
10 changes: 7 additions & 3 deletions epaint/src/image.rs
@@ -1,4 +1,4 @@
use crate::Color32;
use crate::{textures::TextureFilter, Color32};

/// An image stored in RAM.
///
Expand Down Expand Up @@ -272,6 +272,8 @@ 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,

/// 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`].
Expand All @@ -280,17 +282,19 @@ pub struct ImageDelta {

impl ImageDelta {
/// Update the whole texture.
pub fn full(image: impl Into<ImageData>) -> Self {
pub fn full(image: impl Into<ImageData>, filter: TextureFilter) -> Self {
Self {
image: image.into(),
filter,
pos: None,
}
}

/// 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 {
image: image.into(),
filter,
pos: Some(pos),
}
}
Expand Down
6 changes: 3 additions & 3 deletions epaint/src/texture_atlas.rs
@@ -1,6 +1,6 @@
use emath::{remap_clamp, Rect};

use crate::{FontImage, ImageDelta};
use crate::{textures::TextureFilter, FontImage, ImageDelta};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct Rectu {
Expand Down Expand Up @@ -176,12 +176,12 @@ impl TextureAtlas {
if dirty == Rectu::NOTHING {
None
} else if dirty == Rectu::EVERYTHING {
Some(ImageDelta::full(self.image.clone()))
Some(ImageDelta::full(self.image.clone(), TextureFilter::Linear))
} 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))
Some(ImageDelta::partial(pos, region, TextureFilter::Linear))
}
}

Expand Down
18 changes: 13 additions & 5 deletions epaint/src/texture_handle.rs
@@ -1,6 +1,9 @@
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.
///
Expand Down Expand Up @@ -63,17 +66,22 @@ impl TextureHandle {
}

/// 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
.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.
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
.write()
.set(self.id, ImageDelta::partial(pos, image.into()));
.set(self.id, ImageDelta::partial(pos, image.into(), filter));
}

/// width x height
Expand Down
21 changes: 19 additions & 2 deletions epaint/src/textures.rs
Expand Up @@ -27,7 +27,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) -> TextureId {
pub fn alloc(&mut self, name: String, image: ImageData, filter: TextureFilter) -> TextureId {
let id = TextureId::Managed(self.next_id);
self.next_id += 1;

Expand All @@ -36,9 +36,10 @@ impl TextureManager {
size: image.size(),
bytes_per_pixel: image.bytes_per_pixel(),
retain_count: 1,
filter,
});

self.delta.set.insert(id, ImageDelta::full(image));
self.delta.set.insert(id, ImageDelta::full(image, filter));
id
}

Expand Down Expand Up @@ -130,6 +131,22 @@ pub struct TextureMeta {

/// Free when this reaches zero.
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 {
Expand Down

0 comments on commit b4df5e7

Please sign in to comment.