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:
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:72e133c853ab933d37711665ce1278f8f23ce35b8a74a46559f269d0dfd0bf40
|
||||
size 353
|
||||
Reference in New Issue
Block a user