1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 04:40:03 -04:00

Make kittest's predictable texture filtering honor TextureOptions::NEAREST (#8441)

This commit is contained in:
Emil Ernerfeldt
2026-08-21 01:33:29 -07:00
committed by GitHub
parent 38c4ab7b3d
commit 00dc6e814b
4 changed files with 95 additions and 2 deletions

View File

@@ -98,20 +98,31 @@ fn vs_main(
@group(1) @binding(0) var r_tex_color: texture_2d<f32>;
@group(1) @binding(1) var r_tex_sampler: sampler;
/// 1 if the texture sampler uses nearest filtering, 0 if linear.
/// Only read when `predictable_texture_filtering` is on.
@group(1) @binding(2) var<uniform> r_tex_nearest_filtering: u32;
fn sample_texture(in: VertexOutput) -> vec4<f32> {
if r_locals.predictable_texture_filtering == 0 {
// Hardware filtering: fast, but varies across GPUs and drivers.
return textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
} else {
// Manual bilinear filtering with four taps at pixel centers using textureLoad
let texture_size = vec2<i32>(textureDimensions(r_tex_color, 0));
let texture_size_f = vec2<f32>(texture_size);
let max_coord = texture_size - vec2<i32>(1, 1);
if r_tex_nearest_filtering == 1 {
// Nearest filtering: load the texel under the sample position.
let texel = clamp(vec2<i32>(in.tex_coord * texture_size_f), vec2<i32>(0, 0), max_coord);
return textureLoad(r_tex_color, texel, 0);
}
// Manual bilinear filtering with four taps at pixel centers using textureLoad
let pixel_coord = in.tex_coord * texture_size_f - 0.5;
let pixel_fract = fract(pixel_coord);
let pixel_floor = vec2<i32>(floor(pixel_coord));
// Manual texture clamping
let max_coord = texture_size - vec2<i32>(1, 1);
let p00 = clamp(pixel_floor + vec2<i32>(0, 0), vec2<i32>(0, 0), max_coord);
let p10 = clamp(pixel_floor + vec2<i32>(1, 0), vec2<i32>(0, 0), max_coord);
let p01 = clamp(pixel_floor + vec2<i32>(0, 1), vec2<i32>(0, 0), max_coord);

View File

@@ -245,6 +245,12 @@ pub struct Renderer {
uniform_bind_group: wgpu::BindGroup,
texture_bind_group_layout: wgpu::BindGroupLayout,
/// Uniform buffers each holding a single `u32`:
/// 1 if the texture sampler uses nearest filtering, 0 otherwise.
/// Indexed by that flag value.
/// Read by the shader when `predictable_texture_filtering` is on.
nearest_filtering_flag_buffers: [wgpu::Buffer; 2],
/// Map of egui texture IDs to textures and their associated bindgroups (texture view +
/// sampler). The texture may be None if the `TextureId` is just a handle to a user-provided
/// sampler.
@@ -347,10 +353,28 @@ impl Renderer {
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(core::mem::size_of::<u32>() as _),
ty: wgpu::BufferBindingType::Uniform,
},
count: None,
},
],
})
};
let nearest_filtering_flag_buffers = [0_u32, 1_u32].map(|flag| {
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("egui_nearest_filtering_flag_{flag}")),
contents: bytemuck::bytes_of(&flag),
usage: wgpu::BufferUsages::UNIFORM,
})
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("egui_pipeline_layout"),
bind_group_layouts: &[
@@ -458,6 +482,7 @@ impl Renderer {
previous_uniform_buffer_content: UniformBuffer::zeroed(),
uniform_bind_group,
texture_bind_group_layout,
nearest_filtering_flag_buffers,
textures: HashMap::default(),
next_user_texture_id: 0,
samplers: HashMap::default(),
@@ -709,6 +734,8 @@ impl Renderer {
};
let bind_group = bind_group.unwrap_or_else(|| {
let nearest =
image_delta.options.magnification == epaint::textures::TextureFilter::Nearest;
let sampler = self
.samplers
.entry(image_delta.options)
@@ -727,6 +754,11 @@ impl Renderer {
binding: 1,
resource: wgpu::BindingResource::Sampler(sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: self.nearest_filtering_flag_buffers[usize::from(nearest)]
.as_entire_binding(),
},
],
})
});
@@ -829,6 +861,7 @@ impl Renderer {
) -> epaint::TextureId {
profiling::function_scope!();
let nearest = sampler_descriptor.mag_filter == wgpu::FilterMode::Nearest;
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
compare: None,
..sampler_descriptor
@@ -846,6 +879,11 @@ impl Renderer {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: self.nearest_filtering_flag_buffers[usize::from(nearest)]
.as_entire_binding(),
},
],
});
@@ -885,6 +923,7 @@ impl Renderer {
.get_mut(&id)
.expect("Tried to update a texture that has not been allocated yet.");
let nearest = sampler_descriptor.mag_filter == wgpu::FilterMode::Nearest;
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
compare: None,
..sampler_descriptor
@@ -902,6 +941,11 @@ impl Renderer {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: self.nearest_filtering_flag_buffers[usize::from(nearest)]
.as_entire_binding(),
},
],
});

View File

@@ -1,6 +1,41 @@
use egui::{Color32, accesskit::Role};
use egui_kittest::{Harness, kittest::Queryable as _};
/// Textures with [`egui::TextureOptions::NEAREST`] should render crisp,
/// also with kittest's predictable texture filtering.
#[test]
fn test_nearest_texture_filtering() {
let mut texture: Option<egui::TextureHandle> = None;
let mut harness = Harness::builder()
.with_size(egui::Vec2::new(80.0, 48.0))
.build_ui(move |ui| {
let texture = texture.get_or_insert_with(|| {
let pixels = [
Color32::BLACK,
Color32::WHITE,
Color32::BLACK,
Color32::WHITE,
Color32::WHITE,
Color32::BLACK,
Color32::WHITE,
Color32::BLACK,
];
let image = egui::ColorImage::new([4, 2], pixels.to_vec());
ui.ctx()
.load_texture("checkerboard", image, egui::TextureOptions::NEAREST)
});
let rect = egui::Rect::from_min_size(egui::pos2(8.0, 8.0), egui::vec2(64.0, 32.0));
let uv = egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0));
ui.painter().add(
egui::epaint::RectShape::filled(rect, 0, Color32::WHITE)
.with_texture(texture.id(), uv),
);
});
harness.run();
harness.snapshot("nearest_texture_filtering");
}
#[test]
fn test_kerning() {
let mut results = egui_kittest::SnapshotResults::new();

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:72e133c853ab933d37711665ce1278f8f23ce35b8a74a46559f269d0dfd0bf40
size 353