Skip to content

Commit 1cd91ff

Browse files
authored
BGR(A) image format support (#7238)
* Fixes #2340 Adds a checklist item that goes through all the different format to make sure they run properly - this looks a bit overzealous, but in fact we are using the fact that wgpu has special support for 8bit bgr images in order to support them on meshes (this detail is tbf untested as of now!), so going through our formats in a test has quite some merit. (aaalso I think it's the only place where we actually check now that all datatypes still work fine 😳 ) ![image](https://github.com/user-attachments/assets/49b3b8a3-469d-48ce-a6b6-3205f682d902) ("ethnically sourced lena picture", courtesy of Morten Rieger, https://mortenhannemose.github.io/lena/) Speaking of meshes: the mesh texture support story is now spelled out better, showing warnings and ignoring textures if they're in an unsupported format.
1 parent fb38486 commit 1cd91ff

File tree

29 files changed

+401
-108
lines changed

29 files changed

+401
-108
lines changed

crates/store/re_types/definitions/rerun/datatypes/color_model.fbs

+6
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@ enum ColorModel: ubyte{
1515

1616
/// Red, Green, Blue, Alpha
1717
RGBA = 3,
18+
19+
/// Blue, Green, Red
20+
BGR,
21+
22+
/// Blue, Green, Red, Alpha
23+
BGRA,
1824
}

crates/store/re_types/src/archetypes/image_ext.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ impl Image {
3939

4040
let is_shape_correct = match color_model {
4141
ColorModel::L => non_empty_dim_inds.len() == 2,
42-
ColorModel::RGB => {
42+
ColorModel::RGB | ColorModel::BGR => {
4343
non_empty_dim_inds.len() == 3 && shape[non_empty_dim_inds[2]].size == 3
4444
}
45-
ColorModel::RGBA => {
45+
ColorModel::RGBA | ColorModel::BGRA => {
4646
non_empty_dim_inds.len() == 3 && shape[non_empty_dim_inds[2]].size == 4
4747
}
4848
};

crates/store/re_types/src/datatypes/color_model.rs

+15-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/store/re_types/src/datatypes/color_model_ext.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ impl ColorModel {
88
pub fn num_channels(self) -> usize {
99
match self {
1010
Self::L => 1,
11-
Self::RGB => 3,
12-
Self::RGBA => 4,
11+
Self::RGB | Self::BGR => 3,
12+
Self::RGBA | Self::BGRA => 4,
1313
}
1414
}
1515

1616
/// Do we have an alpha channel?
1717
#[inline]
1818
pub fn has_alpha(&self) -> bool {
1919
match self {
20-
Self::L | Self::RGB => false,
21-
Self::RGBA => true,
20+
Self::L | Self::RGB | Self::BGR => false,
21+
Self::RGBA | Self::BGRA => true,
2222
}
2323
}
2424
}

crates/viewer/re_data_ui/src/image.rs

+46
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,52 @@ fn image_pixel_value_ui(
421421
None
422422
}
423423
}
424+
425+
ColorModel::BGR => {
426+
if let Some([b, g, r]) = {
427+
if let [Some(b), Some(g), Some(r)] = [
428+
image.get_xyc(x, y, 0),
429+
image.get_xyc(x, y, 1),
430+
image.get_xyc(x, y, 2),
431+
] {
432+
Some([r, g, b])
433+
} else {
434+
None
435+
}
436+
} {
437+
match (b, g, r) {
438+
(TensorElement::U8(b), TensorElement::U8(g), TensorElement::U8(r)) => {
439+
Some(format!("B: {b}, G: {g}, R: {r}, #{b:02X}{g:02X}{r:02X}"))
440+
}
441+
_ => Some(format!("B: {b}, G: {g}, R: {r}")),
442+
}
443+
} else {
444+
None
445+
}
446+
}
447+
448+
ColorModel::BGRA => {
449+
if let (Some(b), Some(g), Some(r), Some(a)) = (
450+
image.get_xyc(x, y, 0),
451+
image.get_xyc(x, y, 1),
452+
image.get_xyc(x, y, 2),
453+
image.get_xyc(x, y, 3),
454+
) {
455+
match (b, g, r, a) {
456+
(
457+
TensorElement::U8(b),
458+
TensorElement::U8(g),
459+
TensorElement::U8(r),
460+
TensorElement::U8(a),
461+
) => Some(format!(
462+
"B: {b}, G: {g}, R: {r}, A: {a}, #{r:02X}{g:02X}{b:02X}{a:02X}"
463+
)),
464+
_ => Some(format!("B: {b}, G: {g}, R: {r}, A: {a}")),
465+
}
466+
} else {
467+
None
468+
}
469+
}
424470
},
425471
};
426472

crates/viewer/re_renderer/shader/rectangle.wgsl

+3
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ struct UniformBuffer {
5959

6060
/// Boolean: multiply RGB with alpha before filtering
6161
multiply_rgb_with_alpha: u32,
62+
63+
/// Boolean: swizzle RGBA to BGRA
64+
bgra_to_rgba: u32,
6265
};
6366

6467
@group(1) @binding(0)

crates/viewer/re_renderer/shader/rectangle_fs.wgsl

+5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ fn decode_color(sampled_value: vec4f) -> vec4f {
2828
// Normalize the value first, otherwise premultiplying alpha and linear space conversion won't make sense.
2929
var rgba = normalize_range(sampled_value);
3030

31+
// BGR(A) -> RGB(A)
32+
if rect_info.bgra_to_rgba != 0u {
33+
rgba = rgba.bgra;
34+
}
35+
3136
// Convert to linear space
3237
if rect_info.decode_srgb != 0u {
3338
if all(vec3f(0.0) <= rgba.rgb) && all(rgba.rgb <= vec3f(1.0)) {

crates/viewer/re_renderer/src/renderer/rectangles.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ pub enum TextureFilterMin {
5151
pub enum ShaderDecoding {
5252
Nv12,
5353
Yuy2,
54+
55+
/// BGR(A)->RGB(A) conversion is done in the shader.
56+
/// (as opposed to doing it via ``)
57+
Bgr,
5458
}
5559

5660
/// Describes a texture and how to map it to a color.
@@ -151,7 +155,7 @@ impl ColormappedTexture {
151155
let [width, height] = self.texture.width_height();
152156
[width / 2, height]
153157
}
154-
_ => self.texture.width_height(),
158+
Some(ShaderDecoding::Bgr) | None => self.texture.width_height(),
155159
}
156160
}
157161
}
@@ -275,7 +279,8 @@ mod gpu_data {
275279

276280
decode_srgb: u32,
277281
multiply_rgb_with_alpha: u32,
278-
_row_padding: [u32; 2],
282+
bgra_to_rgba: u32,
283+
_row_padding: [u32; 1],
279284

280285
_end_padding: [wgpu_buffer_types::PaddingRow; 16 - 7],
281286
}
@@ -362,6 +367,7 @@ mod gpu_data {
362367
super::TextureFilterMag::Linear => FILTER_BILINEAR,
363368
super::TextureFilterMag::Nearest => FILTER_NEAREST,
364369
};
370+
let bgra_to_rgba = shader_decoding == &Some(super::ShaderDecoding::Bgr);
365371

366372
Ok(Self {
367373
top_left_corner_position: (*top_left_corner_position).into(),
@@ -379,6 +385,7 @@ mod gpu_data {
379385
magnification_filter,
380386
decode_srgb: *decode_srgb as _,
381387
multiply_rgb_with_alpha: *multiply_rgb_with_alpha as _,
388+
bgra_to_rgba: bgra_to_rgba as _,
382389
_row_padding: Default::default(),
383390
_end_padding: Default::default(),
384391
})

crates/viewer/re_space_view_spatial/src/mesh_loader.rs

+50-16
Original file line numberDiff line numberDiff line change
@@ -150,26 +150,19 @@ impl LoadedMesh {
150150
re_math::BoundingBox::from_points(vertex_positions.iter().copied())
151151
};
152152

153-
let albedo = if let (Some(albedo_texture_buffer), Some(albedo_texture_format)) =
154-
(&albedo_texture_buffer, albedo_texture_format)
155-
{
156-
let image_info = ImageInfo {
157-
buffer_row_id: RowId::ZERO, // unused
158-
buffer: albedo_texture_buffer.0.clone(),
159-
format: albedo_texture_format.0,
160-
kind: re_types::image::ImageKind::Color,
161-
colormap: None,
162-
};
163-
re_viewer_context::gpu_bridge::get_or_create_texture(render_ctx, texture_key, || {
164-
let debug_name = "mesh albedo texture";
165-
texture_creation_desc_from_color_image(&image_info, debug_name)
166-
})?
167-
} else {
153+
let albedo = try_get_or_create_albedo_texture(
154+
albedo_texture_buffer,
155+
albedo_texture_format,
156+
render_ctx,
157+
texture_key,
158+
&name,
159+
)
160+
.unwrap_or_else(|| {
168161
render_ctx
169162
.texture_manager_2d
170163
.white_texture_unorm_handle()
171164
.clone()
172-
};
165+
});
173166

174167
let mesh = re_renderer::mesh::Mesh {
175168
label: name.clone().into(),
@@ -211,3 +204,44 @@ impl LoadedMesh {
211204
self.bbox
212205
}
213206
}
207+
208+
fn try_get_or_create_albedo_texture(
209+
albedo_texture_buffer: &Option<re_types::components::ImageBuffer>,
210+
albedo_texture_format: &Option<re_types::components::ImageFormat>,
211+
render_ctx: &RenderContext,
212+
texture_key: u64,
213+
name: &str,
214+
) -> Option<re_renderer::resource_managers::GpuTexture2D> {
215+
let (Some(albedo_texture_buffer), Some(albedo_texture_format)) =
216+
(&albedo_texture_buffer, albedo_texture_format)
217+
else {
218+
return None;
219+
};
220+
221+
let image_info = ImageInfo {
222+
buffer_row_id: RowId::ZERO, // unused
223+
buffer: albedo_texture_buffer.0.clone(),
224+
format: albedo_texture_format.0,
225+
kind: re_types::image::ImageKind::Color,
226+
colormap: None,
227+
};
228+
229+
if re_viewer_context::gpu_bridge::required_shader_decode(albedo_texture_format).is_some() {
230+
re_log::warn_once!("Mesh can't yet handle encoded image formats like NV12 & YUY2 or BGR(A) formats without a channel type other than U8. Ignoring the texture at {name:?}.");
231+
return None;
232+
}
233+
234+
let texture =
235+
re_viewer_context::gpu_bridge::get_or_create_texture(render_ctx, texture_key, || {
236+
let debug_name = "mesh albedo texture";
237+
texture_creation_desc_from_color_image(&image_info, debug_name)
238+
});
239+
240+
match texture {
241+
Ok(texture) => Some(texture),
242+
Err(err) => {
243+
re_log::warn_once!("Failed to create mesh albedo texture for {name:?}: {err}");
244+
None
245+
}
246+
}
247+
}

0 commit comments

Comments
 (0)