Add the Ability to Specify Egui Texture Filters (#1636)
Only works for egui_glow
This commit is contained in:
parent
f3e305a646
commit
1a9a0d7ec8
|
|
@ -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, *};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
|
@ -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());
|
||||
|
||||
|
|
@ -693,7 +694,11 @@ impl Context {
|
|||
/// fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
/// let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
|
||||
/// // 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:
|
||||
|
|
@ -707,6 +712,7 @@ impl Context {
|
|||
&self,
|
||||
name: impl Into<String>,
|
||||
image: impl Into<ImageData>,
|
||||
filter: TextureFilter,
|
||||
) -> TextureHandle {
|
||||
let name = name.into();
|
||||
let image = image.into();
|
||||
|
|
@ -720,7 +726,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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1526,7 +1526,11 @@ impl Ui {
|
|||
/// fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
/// let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
|
||||
/// // 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:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@ use emath::Rot2;
|
|||
/// fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
/// let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
|
||||
/// // 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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -334,6 +334,7 @@ impl TextureManager {
|
|||
size: [width, height],
|
||||
pixels,
|
||||
},
|
||||
TextureFilter::Linear,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use egui::epaint::textures::TextureFilter;
|
||||
use egui::mutex::Mutex;
|
||||
|
||||
/// An image to be shown in egui.
|
||||
|
|
@ -12,8 +13,8 @@ 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 {
|
||||
|
|
@ -21,9 +22,39 @@ impl RetainedImage {
|
|||
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
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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,
|
||||
|
|
@ -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,
|
||||
|
|
@ -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) {
|
||||
|
|
@ -488,7 +472,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, data);
|
||||
}
|
||||
egui::ImageData::Font(image) => {
|
||||
assert_eq!(
|
||||
|
|
@ -507,12 +491,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, &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,
|
||||
|
|
@ -532,12 +522,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(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::Color32;
|
||||
use crate::{textures::TextureFilter, Color32};
|
||||
|
||||
/// 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`].
|
||||
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`].
|
||||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue