1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 05: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(),
},
],
});