Skip to content

Commit 47a4374

Browse files
authored
Use software texture filtering in kittest (#7602)
1 parent 718a82b commit 47a4374

26 files changed

+103
-60
lines changed

crates/eframe/src/native/wgpu_integration.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ impl<'app> WgpuWinitApp<'app> {
194194
self.native_options.stencil_buffer,
195195
),
196196
dithering: self.native_options.dithering,
197+
..Default::default()
197198
},
198199
));
199200

crates/egui-wgpu/src/egui.wgsl

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ struct VertexOutput {
88

99
struct Locals {
1010
screen_size: vec2<f32>,
11-
dithering: u32, // 1 if dithering is enabled, 0 otherwise
12-
// Uniform buffers need to be at least 16 bytes in WebGL.
13-
// See https://github.com/gfx-rs/wgpu/issues/2072
14-
_padding: u32,
11+
12+
/// 1 if dithering is enabled, 0 otherwise
13+
dithering: u32,
14+
15+
/// 1 to do manual filtering for more predictable kittest snapshot images.
16+
/// See also https://github.com/emilk/egui/issues/5295
17+
predictable_texture_filtering: u32,
1518
};
1619
@group(0) @binding(0) var<uniform> r_locals: Locals;
1720

@@ -95,10 +98,42 @@ fn vs_main(
9598
@group(1) @binding(0) var r_tex_color: texture_2d<f32>;
9699
@group(1) @binding(1) var r_tex_sampler: sampler;
97100

101+
fn sample_texture(in: VertexOutput) -> vec4<f32> {
102+
if r_locals.predictable_texture_filtering == 0 {
103+
// Hardware filtering: fast, but varies across GPUs and drivers.
104+
return textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
105+
} else {
106+
// Manual bilinear filtering with four taps at pixel centers using textureLoad
107+
let texture_size = vec2<i32>(textureDimensions(r_tex_color, 0));
108+
let texture_size_f = vec2<f32>(texture_size);
109+
let pixel_coord = in.tex_coord * texture_size_f - 0.5;
110+
let pixel_fract = fract(pixel_coord);
111+
let pixel_floor = vec2<i32>(floor(pixel_coord));
112+
113+
// Manual texture clamping
114+
let max_coord = texture_size - vec2<i32>(1, 1);
115+
let p00 = clamp(pixel_floor + vec2<i32>(0, 0), vec2<i32>(0, 0), max_coord);
116+
let p10 = clamp(pixel_floor + vec2<i32>(1, 0), vec2<i32>(0, 0), max_coord);
117+
let p01 = clamp(pixel_floor + vec2<i32>(0, 1), vec2<i32>(0, 0), max_coord);
118+
let p11 = clamp(pixel_floor + vec2<i32>(1, 1), vec2<i32>(0, 0), max_coord);
119+
120+
// Load at pixel centers
121+
let tl = textureLoad(r_tex_color, p00, 0);
122+
let tr = textureLoad(r_tex_color, p10, 0);
123+
let bl = textureLoad(r_tex_color, p01, 0);
124+
let br = textureLoad(r_tex_color, p11, 0);
125+
126+
// Manual bilinear interpolation
127+
let top = mix(tl, tr, pixel_fract.x);
128+
let bottom = mix(bl, br, pixel_fract.x);
129+
return mix(top, bottom, pixel_fract.y);
130+
}
131+
}
132+
98133
@fragment
99134
fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4<f32> {
100135
// We expect "normal" textures that are NOT sRGB-aware.
101-
let tex_gamma = textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
136+
let tex_gamma = sample_texture(in);
102137
var out_color_gamma = in.color * tex_gamma;
103138
// Dither the float color down to eight bits to reduce banding.
104139
// This step is optional for egui backends.
@@ -115,7 +150,7 @@ fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4<f32> {
115150
@fragment
116151
fn fs_main_gamma_framebuffer(in: VertexOutput) -> @location(0) vec4<f32> {
117152
// We expect "normal" textures that are NOT sRGB-aware.
118-
let tex_gamma = textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
153+
let tex_gamma = sample_texture(in);
119154
var out_color_gamma = in.color * tex_gamma;
120155
// Dither the float color down to eight bits to reduce banding.
121156
// This step is optional for egui backends.

crates/egui-wgpu/src/renderer.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -141,21 +141,16 @@ impl ScreenDescriptor {
141141
}
142142

143143
/// Uniform buffer used when rendering.
144-
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
144+
#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
145145
#[repr(C)]
146146
struct UniformBuffer {
147147
screen_size_in_points: [f32; 2],
148148
dithering: u32,
149-
// Uniform buffers need to be at least 16 bytes in WebGL.
150-
// See https://github.com/gfx-rs/wgpu/issues/2072
151-
_padding: u32,
152-
}
153149

154-
impl PartialEq for UniformBuffer {
155-
fn eq(&self, other: &Self) -> bool {
156-
self.screen_size_in_points == other.screen_size_in_points
157-
&& self.dithering == other.dithering
158-
}
150+
/// 1 to do manual filtering for more predictable kittest snapshot images.
151+
///
152+
/// See also <https://github.com/emilk/egui/issues/5295>.
153+
predictable_texture_filtering: u32,
159154
}
160155

161156
struct SlicedBuffer {
@@ -204,6 +199,16 @@ pub struct RendererOptions {
204199
///
205200
/// Defaults to true.
206201
pub dithering: bool,
202+
203+
/// Perform texture filtering in software?
204+
///
205+
/// This is useful when you want predictable rendering across
206+
/// different hardware, e.g. for kittest snapshots.
207+
///
208+
/// Default is `false`.
209+
///
210+
/// See also <https://github.com/emilk/egui/issues/5295>.
211+
pub predictable_texture_filtering: bool,
207212
}
208213

209214
impl RendererOptions {
@@ -214,6 +219,7 @@ impl RendererOptions {
214219
msaa_samples: 1,
215220
depth_stencil_format: None,
216221
dithering: false,
222+
predictable_texture_filtering: true,
217223
};
218224
}
219225

@@ -223,6 +229,7 @@ impl Default for RendererOptions {
223229
msaa_samples: 0,
224230
depth_stencil_format: None,
225231
dithering: true,
232+
predictable_texture_filtering: false,
226233
}
227234
}
228235
}
@@ -280,7 +287,7 @@ impl Renderer {
280287
contents: bytemuck::cast_slice(&[UniformBuffer {
281288
screen_size_in_points: [0.0, 0.0],
282289
dithering: u32::from(options.dithering),
283-
_padding: Default::default(),
290+
predictable_texture_filtering: u32::from(options.predictable_texture_filtering),
284291
}]),
285292
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
286293
});
@@ -895,7 +902,7 @@ impl Renderer {
895902
let uniform_buffer_content = UniformBuffer {
896903
screen_size_in_points,
897904
dithering: u32::from(self.options.dithering),
898-
_padding: Default::default(),
905+
predictable_texture_filtering: u32::from(self.options.predictable_texture_filtering),
899906
};
900907
if uniform_buffer_content != self.previous_uniform_buffer_content {
901908
profiling::scope!("update uniforms");
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading

0 commit comments

Comments
 (0)