From a5973e5cac461a23c853cb174b28c8e9317ecce6 Mon Sep 17 00:00:00 2001 From: Dan Lock Date: Mon, 5 Feb 2024 08:37:05 +0000 Subject: [PATCH] Add `TextureOptions::wrap_mode` (#3954) Exposes support in both glow and wgpu for texture wrap modes This would be breaking for manual creations of TextureOptions but would work with the current TextureOptions::NEAREST and LINEAR without change, keeping those clamp to edge I wasn't sure how best to expose the options to the user and added consts for LINEAR_REPEAT LINEAR_MIRRORED_REPEAT NEAREST_REPEAT NEAREST_MIRRORED_REPEAT This does not include wrap mode clamp to border as it worked fine with glow but with wgpu it panics due to Features Features(ADDRESS_MODE_CLAMP_TO_BORDER) are required but not enabled on the device, and I thought it was probably best not to try to enable that feature, but happy to include that functionality also if that is okay to be toggled ![image](https://github.com/emilk/egui/assets/5075747/bba71f61-a105-4e5b-b8ce-1083621eb3de) --------- Co-authored-by: Emil Ernerfeldt --- crates/egui-wgpu/src/renderer.rs | 7 +++++ crates/egui/src/lib.rs | 2 +- crates/egui_glow/src/painter.rs | 18 ++++++++++-- crates/epaint/src/textures.rs | 50 ++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 21227ef9058..bb0d4581cf2 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -923,12 +923,19 @@ fn create_sampler( epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest, epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear, }; + let address_mode = match options.wrap_mode { + epaint::textures::TextureWrapMode::ClampToEdge => wgpu::AddressMode::ClampToEdge, + epaint::textures::TextureWrapMode::Repeat => wgpu::AddressMode::Repeat, + epaint::textures::TextureWrapMode::MirroredRepeat => wgpu::AddressMode::MirrorRepeat, + }; device.create_sampler(&wgpu::SamplerDescriptor { label: Some(&format!( "egui sampler (mag: {mag_filter:?}, min {min_filter:?})" )), mag_filter, min_filter, + address_mode_u: address_mode, + address_mode_v: address_mode, ..Default::default() }) } diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 73d51561d2d..ad931885ea8 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -395,7 +395,7 @@ pub use emath::{ pub use epaint::{ mutex, text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, - textures::{TextureFilter, TextureOptions, TexturesDelta}, + textures::{TextureFilter, TextureOptions, TextureWrapMode, TexturesDelta}, ClippedPrimitive, ColorImage, FontImage, ImageData, Mesh, PaintCallback, PaintCallbackInfo, Rounding, Shape, Stroke, TextureHandle, TextureId, }; diff --git a/crates/egui_glow/src/painter.rs b/crates/egui_glow/src/painter.rs index 48c90050a20..b1278ac39d5 100644 --- a/crates/egui_glow/src/painter.rs +++ b/crates/egui_glow/src/painter.rs @@ -34,6 +34,20 @@ impl TextureFilterExt for egui::TextureFilter { } } +trait TextureWrapModeExt { + fn glow_code(&self) -> u32; +} + +impl TextureWrapModeExt for egui::TextureWrapMode { + fn glow_code(&self) -> u32 { + match self { + Self::ClampToEdge => glow::CLAMP_TO_EDGE, + Self::Repeat => glow::REPEAT, + Self::MirroredRepeat => glow::MIRRORED_REPEAT, + } + } +} + #[derive(Debug)] pub struct PainterError(String); @@ -555,12 +569,12 @@ impl Painter { self.gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, - glow::CLAMP_TO_EDGE as i32, + options.wrap_mode.glow_code() as i32, ); self.gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, - glow::CLAMP_TO_EDGE as i32, + options.wrap_mode.glow_code() as i32, ); check_for_gl_error!(&self.gl, "tex_parameter"); diff --git a/crates/epaint/src/textures.rs b/crates/epaint/src/textures.rs index e1979507b9f..e4661ff02b7 100644 --- a/crates/epaint/src/textures.rs +++ b/crates/epaint/src/textures.rs @@ -156,6 +156,9 @@ pub struct TextureOptions { /// How to filter when minifying (when texels are smaller than pixels). pub minification: TextureFilter, + + /// How to wrap the texture when the texture coordinates are outside the [0, 1] range. + pub wrap_mode: TextureWrapMode, } impl TextureOptions { @@ -163,12 +166,42 @@ impl TextureOptions { pub const LINEAR: Self = Self { magnification: TextureFilter::Linear, minification: TextureFilter::Linear, + wrap_mode: TextureWrapMode::ClampToEdge, }; /// Nearest magnification and minification. pub const NEAREST: Self = Self { magnification: TextureFilter::Nearest, minification: TextureFilter::Nearest, + wrap_mode: TextureWrapMode::ClampToEdge, + }; + + /// Linear magnification and minification, but with the texture repeated. + pub const LINEAR_REPEAT: Self = Self { + magnification: TextureFilter::Linear, + minification: TextureFilter::Linear, + wrap_mode: TextureWrapMode::Repeat, + }; + + /// Linear magnification and minification, but with the texture mirrored and repeated. + pub const LINEAR_MIRRORED_REPEAT: Self = Self { + magnification: TextureFilter::Linear, + minification: TextureFilter::Linear, + wrap_mode: TextureWrapMode::MirroredRepeat, + }; + + /// Nearest magnification and minification, but with the texture repeated. + pub const NEAREST_REPEAT: Self = Self { + magnification: TextureFilter::Nearest, + minification: TextureFilter::Nearest, + wrap_mode: TextureWrapMode::Repeat, + }; + + /// Nearest magnification and minification, but with the texture mirrored and repeated. + pub const NEAREST_MIRRORED_REPEAT: Self = Self { + magnification: TextureFilter::Nearest, + minification: TextureFilter::Nearest, + wrap_mode: TextureWrapMode::MirroredRepeat, }; } @@ -193,6 +226,23 @@ pub enum TextureFilter { Linear, } +/// Defines how textures are wrapped around objects when texture coordinates fall outside the [0, 1] range. +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum TextureWrapMode { + /// Stretches the edge pixels to fill beyond the texture's bounds. + /// + /// This is what you want to use for a normal image in a GUI. + #[default] + ClampToEdge, + + /// Tiles the texture across the surface, repeating it horizontally and vertically. + Repeat, + + /// Mirrors the texture with each repetition, creating symmetrical tiling. + MirroredRepeat, +} + // ---------------------------------------------------------------------------- /// What has been allocated and freed during the last period.