Skip to content

Commit d33dab6

Browse files
authored
Implement billinear filtering of textures (#1850)
* Implement opt-in billinear filtering of textures * bilinear
1 parent beffbc3 commit d33dab6

File tree

3 files changed

+97
-32
lines changed

3 files changed

+97
-32
lines changed

crates/re_renderer/shader/rectangle.wgsl

+60-6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ const COLOR_MAPPER_OFF = 1u;
1616
const COLOR_MAPPER_FUNCTION = 2u;
1717
const COLOR_MAPPER_TEXTURE = 3u;
1818

19+
const FILTER_NEAREST = 1u;
20+
const FILTER_BILINEAR = 2u;
21+
1922
struct UniformBuffer {
2023
/// Top left corner position in world space.
2124
top_left_corner_position: Vec3,
@@ -48,6 +51,9 @@ struct UniformBuffer {
4851
/// Exponent to raise the normalized texture value.
4952
/// Inverse brightness.
5053
gamma: f32,
54+
55+
minification_filter: u32,
56+
magnification_filter: u32,
5157
};
5258

5359
@group(1) @binding(0)
@@ -90,6 +96,18 @@ fn vs_main(@builtin(vertex_index) v_idx: u32) -> VertexOut {
9096
return out;
9197
}
9298

99+
fn is_magnifying(pixel_coord: Vec2) -> bool {
100+
return fwidth(pixel_coord.x) < 1.0;
101+
}
102+
103+
fn tex_filter(pixel_coord: Vec2) -> u32 {
104+
if is_magnifying(pixel_coord) {
105+
return rect_info.magnification_filter;
106+
} else {
107+
return rect_info.minification_filter;
108+
}
109+
}
110+
93111
@fragment
94112
fn fs_main(in: VertexOut) -> @location(0) Vec4 {
95113
// Sample the main texture:
@@ -98,14 +116,50 @@ fn fs_main(in: VertexOut) -> @location(0) Vec4 {
98116
// TODO(emilk): support mipmaps
99117
sampled_value = textureSampleLevel(texture_float_filterable, texture_sampler, in.texcoord, 0.0);
100118
} else if rect_info.sample_type == SAMPLE_TYPE_FLOAT_NOFILTER {
101-
let icoords = IVec2(in.texcoord * Vec2(textureDimensions(texture_float).xy));
102-
sampled_value = Vec4(textureLoad(texture_float, icoords, 0));
119+
let coord = in.texcoord * Vec2(textureDimensions(texture_float).xy);
120+
if tex_filter(coord) == FILTER_NEAREST {
121+
// nearest
122+
sampled_value = textureLoad(texture_float, IVec2(coord + vec2(0.5)), 0);
123+
} else {
124+
// bilinear
125+
let v00 = textureLoad(texture_float, IVec2(coord) + IVec2(0, 0), 0);
126+
let v01 = textureLoad(texture_float, IVec2(coord) + IVec2(0, 1), 0);
127+
let v10 = textureLoad(texture_float, IVec2(coord) + IVec2(1, 0), 0);
128+
let v11 = textureLoad(texture_float, IVec2(coord) + IVec2(1, 1), 0);
129+
let top = mix(v00, v10, fract(coord.x));
130+
let bottom = mix(v01, v11, fract(coord.x));
131+
sampled_value = mix(top, bottom, fract(coord.y));
132+
}
103133
} else if rect_info.sample_type == SAMPLE_TYPE_SINT_NOFILTER {
104-
let icoords = IVec2(in.texcoord * Vec2(textureDimensions(texture_sint).xy));
105-
sampled_value = Vec4(textureLoad(texture_sint, icoords, 0));
134+
let coord = in.texcoord * Vec2(textureDimensions(texture_sint).xy);
135+
if tex_filter(coord) == FILTER_NEAREST {
136+
// nearest
137+
sampled_value = Vec4(textureLoad(texture_sint, IVec2(coord + vec2(0.5)), 0));
138+
} else {
139+
// bilinear
140+
let v00 = Vec4(textureLoad(texture_sint, IVec2(coord) + IVec2(0, 0), 0));
141+
let v01 = Vec4(textureLoad(texture_sint, IVec2(coord) + IVec2(0, 1), 0));
142+
let v10 = Vec4(textureLoad(texture_sint, IVec2(coord) + IVec2(1, 0), 0));
143+
let v11 = Vec4(textureLoad(texture_sint, IVec2(coord) + IVec2(1, 1), 0));
144+
let top = mix(v00, v10, fract(coord.x));
145+
let bottom = mix(v01, v11, fract(coord.x));
146+
sampled_value = mix(top, bottom, fract(coord.y));
147+
}
106148
} else if rect_info.sample_type == SAMPLE_TYPE_UINT_NOFILTER {
107-
let icoords = IVec2(in.texcoord * Vec2(textureDimensions(texture_uint).xy));
108-
sampled_value = Vec4(textureLoad(texture_uint, icoords, 0));
149+
let coord = in.texcoord * Vec2(textureDimensions(texture_uint).xy);
150+
if tex_filter(coord) == FILTER_NEAREST {
151+
// nearest
152+
sampled_value = Vec4(textureLoad(texture_uint, IVec2(coord + vec2(0.5)), 0));
153+
} else {
154+
// bilinear
155+
let v00 = Vec4(textureLoad(texture_uint, IVec2(coord) + IVec2(0, 0), 0));
156+
let v01 = Vec4(textureLoad(texture_uint, IVec2(coord) + IVec2(0, 1), 0));
157+
let v10 = Vec4(textureLoad(texture_uint, IVec2(coord) + IVec2(1, 0), 0));
158+
let v11 = Vec4(textureLoad(texture_uint, IVec2(coord) + IVec2(1, 1), 0));
159+
let top = mix(v00, v10, fract(coord.x));
160+
let bottom = mix(v01, v11, fract(coord.x));
161+
sampled_value = mix(top, bottom, fract(coord.y));
162+
}
109163
} else {
110164
return ERROR_RGBA; // unknown sample type
111165
}

crates/re_renderer/src/renderer/rectangles.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ mod gpu_data {
192192
const COLOR_MAPPER_FUNCTION: u32 = 2;
193193
const COLOR_MAPPER_TEXTURE: u32 = 3;
194194

195+
const FILTER_NEAREST: u32 = 1;
196+
const FILTER_BILINEAR: u32 = 2;
197+
195198
#[repr(C, align(256))]
196199
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
197200
pub struct UniformBuffer {
@@ -213,7 +216,8 @@ mod gpu_data {
213216

214217
color_mapper: u32,
215218
gamma: f32,
216-
_row_padding: [u32; 2],
219+
minification_filter: u32,
220+
magnification_filter: u32,
217221

218222
_end_padding: [wgpu_buffer_types::PaddingRow; 16 - 6],
219223
}
@@ -275,6 +279,15 @@ mod gpu_data {
275279
}
276280
}
277281

282+
let minification_filter = match rectangle.texture_filter_minification {
283+
super::TextureFilterMin::Linear => FILTER_BILINEAR,
284+
super::TextureFilterMin::Nearest => FILTER_NEAREST,
285+
};
286+
let magnification_filter = match rectangle.texture_filter_magnification {
287+
super::TextureFilterMag::Linear => FILTER_BILINEAR,
288+
super::TextureFilterMag::Nearest => FILTER_NEAREST,
289+
};
290+
278291
Ok(Self {
279292
top_left_corner_position: rectangle.top_left_corner_position.into(),
280293
colormap_function,
@@ -287,7 +300,8 @@ mod gpu_data {
287300
range_min_max: (*range).into(),
288301
color_mapper: color_mapper_int,
289302
gamma: *gamma,
290-
_row_padding: Default::default(),
303+
minification_filter,
304+
magnification_filter,
291305
_end_padding: Default::default(),
292306
})
293307
}

crates/re_viewer/src/ui/view_tensor/ui.rs

+21-24
Original file line numberDiff line numberDiff line change
@@ -426,32 +426,29 @@ impl TextureSettings {
426426
});
427427
ui.end_row();
428428

429-
// TODO(#1612): support texture filtering again
430-
if false {
431-
re_ui
432-
.grid_left_hand_label(ui, "Filtering")
433-
.on_hover_text("Filtering to use when magnifying");
434-
435-
fn tf_to_string(tf: egui::TextureFilter) -> &'static str {
436-
match tf {
437-
egui::TextureFilter::Nearest => "Nearest",
438-
egui::TextureFilter::Linear => "Linear",
439-
}
429+
re_ui
430+
.grid_left_hand_label(ui, "Filtering")
431+
.on_hover_text("Filtering to use when magnifying");
432+
433+
fn tf_to_string(tf: egui::TextureFilter) -> &'static str {
434+
match tf {
435+
egui::TextureFilter::Nearest => "Nearest",
436+
egui::TextureFilter::Linear => "Linear",
440437
}
441-
egui::ComboBox::from_id_source("texture_filter")
442-
.selected_text(tf_to_string(options.magnification))
443-
.show_ui(ui, |ui| {
444-
ui.style_mut().wrap = Some(false);
445-
ui.set_min_width(64.0);
446-
447-
let mut selectable_value = |ui: &mut egui::Ui, e| {
448-
ui.selectable_value(&mut options.magnification, e, tf_to_string(e))
449-
};
450-
selectable_value(ui, egui::TextureFilter::Linear);
451-
selectable_value(ui, egui::TextureFilter::Nearest);
452-
});
453-
ui.end_row();
454438
}
439+
egui::ComboBox::from_id_source("texture_filter")
440+
.selected_text(tf_to_string(options.magnification))
441+
.show_ui(ui, |ui| {
442+
ui.style_mut().wrap = Some(false);
443+
ui.set_min_width(64.0);
444+
445+
let mut selectable_value = |ui: &mut egui::Ui, e| {
446+
ui.selectable_value(&mut options.magnification, e, tf_to_string(e))
447+
};
448+
selectable_value(ui, egui::TextureFilter::Nearest);
449+
selectable_value(ui, egui::TextureFilter::Linear);
450+
});
451+
ui.end_row();
455452
}
456453
}
457454

0 commit comments

Comments
 (0)