Skip to content

Commit d7b6e57

Browse files
authored
Fix wrong rendering of some texture formats in light mode (#11225)
1 parent 4546c13 commit d7b6e57

File tree

7 files changed

+92
-56
lines changed

7 files changed

+92
-56
lines changed

crates/viewer/re_renderer/shader/rectangle.wgsl

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,23 @@ const COLOR_MAPPER_TEXTURE = 4u;
1818
const FILTER_NEAREST = 1u;
1919
const FILTER_BILINEAR = 2u;
2020

21+
// ----------------------------------------------------------------------------
22+
// See enum TextureAlpha
23+
24+
/// Ignore the alpha channel and render the image opaquely.
25+
///
26+
/// Use this for textures that don't have an alpha channel.
27+
const TEXTURE_ALPHA_OPAQUE = 1u;
28+
29+
/// The alpha in the texture is separate/unmulitplied.
30+
///
31+
/// Use this for images with separate (unmulitplied) alpha channels (the normal kind).
32+
const TEXTURE_ALPHA_SEPARATE_ALPHA = 2u;
33+
34+
/// The RGB values have already been premultiplied with the alpha.
35+
const TEXTURE_ALPHA_ALREADY_PREMULTIPLIED = 3u;
36+
// ----------------------------------------------------------------------------
37+
2138
struct UniformBuffer {
2239
/// Top left corner position in world space.
2340
top_left_corner_position: vec3f,
@@ -57,8 +74,8 @@ struct UniformBuffer {
5774
/// Boolean: decode 0-1 sRGB gamma to linear space before filtering?
5875
decode_srgb: u32,
5976

60-
/// Boolean: multiply RGB with alpha before filtering
61-
multiply_rgb_with_alpha: u32,
77+
/// TEXTURE_ALPHA_…
78+
texture_alpha: u32,
6279

6380
/// Boolean: swizzle RGBA to BGRA
6481
bgra_to_rgba: u32,

crates/viewer/re_renderer/shader/rectangle_fs.wgsl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,15 @@ fn decode_color(sampled_value: vec4f) -> vec4f {
3737
}
3838
}
3939

40-
// Premultiply alpha.
41-
if rect_info.multiply_rgb_with_alpha != 0u {
40+
if rect_info.texture_alpha == TEXTURE_ALPHA_OPAQUE {
41+
rgba.a = 1.0; // ignore the alpha in the texture
42+
} else if rect_info.texture_alpha == TEXTURE_ALPHA_SEPARATE_ALPHA {
43+
// Premultiply alpha.
4244
rgba = vec4f(rgba.xyz * rgba.a, rgba.a);
45+
} else if rect_info.texture_alpha == TEXTURE_ALPHA_ALREADY_PREMULTIPLIED {
46+
// All good
47+
} else {
48+
rgba = ERROR_RGBA; // unknown enum
4349
}
4450

4551
return rgba;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub use point_cloud::{
1919
};
2020
pub use rectangles::{
2121
ColorMapper, ColormappedTexture, RectangleDrawData, RectangleOptions, ShaderDecoding,
22-
TextureFilterMag, TextureFilterMin, TexturedRect,
22+
TextureAlpha, TextureFilterMag, TextureFilterMin, TexturedRect,
2323
};
2424
pub use test_triangle::TestTriangleDrawData;
2525
pub use world_grid::{WorldGridConfiguration, WorldGridDrawData, WorldGridRenderer};

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

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,25 @@ pub enum ShaderDecoding {
5555
Bgr,
5656
}
5757

58+
/// How is the alpha channel in the texture?
59+
#[derive(Clone, Copy, Debug)]
60+
pub enum TextureAlpha {
61+
/// Ignore the alpha channel and render the image opaquely.
62+
///
63+
/// Use this for textures that don't have an alpha channel.
64+
Opaque = 1,
65+
66+
/// The alpha in the texture is separate/unmulitplied.
67+
///
68+
/// Use this for images with separate (unmulitplied) alpha channels (the normal kind).
69+
SeparateAlpha = 2,
70+
71+
/// The RGB values have already been premultiplied with the alpha.
72+
AlreadyPremultiplied = 3,
73+
}
74+
5875
/// Describes a texture and how to map it to a color.
59-
#[derive(Clone)]
76+
#[derive(Clone, Debug)]
6077
pub struct ColormappedTexture {
6178
pub texture: GpuTexture2D,
6279

@@ -72,11 +89,8 @@ pub struct ColormappedTexture {
7289
/// Only applies to [`wgpu::TextureFormat::Rgba8Unorm`] and float textures.
7390
pub decode_srgb: bool,
7491

75-
/// Multiply color channels with the alpha channel before filtering?
76-
///
77-
/// Set this to false for textures that don't have an alpha channel or are already pre-multiplied.
78-
/// Applied after range normalization and srgb decoding, before filtering.
79-
pub multiply_rgb_with_alpha: bool,
92+
/// How to treat the alpha channel.
93+
pub texture_alpha: TextureAlpha,
8094

8195
/// Raise the normalized values to this power (before any color mapping).
8296
/// Acts like an inverse brightness.
@@ -137,7 +151,7 @@ impl ColormappedTexture {
137151
decode_srgb,
138152
range: [0.0, 1.0],
139153
gamma: 1.0,
140-
multiply_rgb_with_alpha: true,
154+
texture_alpha: TextureAlpha::SeparateAlpha,
141155
color_mapper: ColorMapper::OffRGB,
142156
shader_decoding: None,
143157
}
@@ -148,7 +162,7 @@ impl ColormappedTexture {
148162
}
149163
}
150164

151-
#[derive(Clone)]
165+
#[derive(Clone, Debug)]
152166
pub struct TexturedRect {
153167
/// Top left corner position in world space.
154168
pub top_left_corner_position: glam::Vec3,
@@ -184,7 +198,7 @@ impl TexturedRect {
184198
}
185199
}
186200

187-
#[derive(Clone)]
201+
#[derive(Clone, Debug)]
188202
pub struct RectangleOptions {
189203
pub texture_filter_magnification: TextureFilterMag,
190204
pub texture_filter_minification: TextureFilterMin,
@@ -282,7 +296,7 @@ mod gpu_data {
282296
magnification_filter: u32,
283297

284298
decode_srgb: u32,
285-
multiply_rgb_with_alpha: u32,
299+
texture_alpha: u32,
286300
bgra_to_rgba: u32,
287301
_row_padding: [u32; 1],
288302

@@ -311,7 +325,7 @@ mod gpu_data {
311325
range,
312326
gamma,
313327
color_mapper,
314-
multiply_rgb_with_alpha,
328+
texture_alpha,
315329
shader_decoding,
316330
} = colormapped_texture;
317331

@@ -380,7 +394,7 @@ mod gpu_data {
380394
minification_filter,
381395
magnification_filter,
382396
decode_srgb: *decode_srgb as _,
383-
multiply_rgb_with_alpha: *multiply_rgb_with_alpha as _,
397+
texture_alpha: *texture_alpha as _,
384398
bgra_to_rgba: bgra_to_rgba as _,
385399
_row_padding: Default::default(),
386400
_end_padding: Default::default(),

crates/viewer/re_view_tensor/src/tensor_slice_to_gpu.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub fn colormapped_texture(
4141
texture,
4242
range: colormap.value_range,
4343
decode_srgb: false,
44-
multiply_rgb_with_alpha: false,
44+
texture_alpha: re_renderer::renderer::TextureAlpha::Opaque,
4545
gamma: *gamma.0,
4646
color_mapper: re_renderer::renderer::ColorMapper::Function(colormap_to_re_renderer(
4747
colormap.colormap,

crates/viewer/re_viewer_context/src/gpu_bridge/colormap.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ fn colormap_preview_ui(
4747
texture: horizontal_gradient,
4848
range: [0.0, 1.0],
4949
decode_srgb: false,
50-
multiply_rgb_with_alpha: false,
50+
texture_alpha: re_renderer::renderer::TextureAlpha::Opaque,
5151
gamma: 1.0,
5252
shader_decoding: None,
5353
color_mapper: re_renderer::renderer::ColorMapper::Function(colormap_to_re_renderer(

crates/viewer/re_viewer_context/src/gpu_bridge/image_to_gpu.rs

Lines changed: 36 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ use std::borrow::Cow;
44

55
use anyhow::Context as _;
66
use egui::{Rangef, util::hash};
7+
use half::f16;
78
use wgpu::TextureFormat;
89

910
use re_renderer::{
1011
RenderContext,
1112
device_caps::DeviceCaps,
1213
pad_rgb_to_rgba,
13-
renderer::{ColorMapper, ColormappedTexture, ShaderDecoding},
14+
renderer::{ColorMapper, ColormappedTexture, ShaderDecoding, TextureAlpha},
1415
resource_managers::{
1516
ImageDataDesc, SourceImageDataFormat, YuvMatrixCoefficients, YuvPixelLayout, YuvRange,
1617
},
@@ -141,21 +142,25 @@ fn color_image_to_gpu(
141142
ColorMapper::OffRGB
142143
};
143144

144-
// Assume that the texture has a separate (non-pre-multiplied) alpha.
145-
// TODO(wumpf): There should be a way to specify whether a texture uses pre-multiplied alpha or not.
146-
let multiply_rgb_with_alpha = image_format.has_alpha();
145+
let texture_alpha = if image_format.has_alpha() {
146+
// Assume that the texture has a separate (non-pre-multiplied) alpha.
147+
// TODO(wumpf): There should be a way to specify whether a texture uses pre-multiplied alpha or not.
148+
TextureAlpha::SeparateAlpha
149+
} else {
150+
TextureAlpha::Opaque
151+
};
147152

148153
let gamma = 1.0;
149154

150155
re_log::trace_once!(
151-
"color_tensor_to_gpu {debug_name:?}, range: {range:?}, decode_srgb: {decode_srgb:?}, multiply_rgb_with_alpha: {multiply_rgb_with_alpha:?}, gamma: {gamma:?}, color_mapper: {color_mapper:?}",
156+
"color_tensor_to_gpu {debug_name:?}, range: {range:?}, decode_srgb: {decode_srgb:?}, texture_alpha: {texture_alpha:?}, gamma: {gamma:?}, color_mapper: {color_mapper:?}",
152157
);
153158

154159
Ok(ColormappedTexture {
155160
texture: texture_handle,
156161
range: [range.min, range.max],
157162
decode_srgb,
158-
multiply_rgb_with_alpha,
163+
texture_alpha,
159164
gamma,
160165
color_mapper,
161166
shader_decoding,
@@ -327,7 +332,7 @@ pub fn texture_creation_desc_from_color_image<'a>(
327332
// Why not use `Rgba8UnormSrgb`? Because premul must happen _before_ sRGB decode, so we can't
328333
// use a "Srgb-aware" texture like `Rgba8UnormSrgb` for RGBA.
329334
(ColorModel::RGB, ChannelDatatype::U8) => (
330-
pad_rgb_to_rgba(&image.buffer, u8::MAX).into(),
335+
pad_rgb_to_rgba(&image.buffer, 0).into(),
331336
SourceImageDataFormat::WgpuCompatible(TextureFormat::Rgba8Unorm),
332337
),
333338
(ColorModel::RGBA, ChannelDatatype::U8) => (
@@ -349,7 +354,7 @@ pub fn texture_creation_desc_from_color_image<'a>(
349354
//
350355
// See also [`required_shader_decode`] which lists this case as a format that does not need to be decoded.
351356
(ColorModel::BGR, ChannelDatatype::U8) => {
352-
let padded_data = pad_rgb_to_rgba(&image.buffer, u8::MAX).into();
357+
let padded_data = pad_rgb_to_rgba(&image.buffer, 0).into();
353358
let texture_format = if required_shader_decode(device_caps, &image.format).is_some()
354359
{
355360
TextureFormat::Rgba8Unorm
@@ -433,7 +438,7 @@ fn depth_image_to_gpu(
433438
texture,
434439
range: value_range,
435440
decode_srgb: false,
436-
multiply_rgb_with_alpha: false,
441+
texture_alpha: TextureAlpha::Opaque,
437442
gamma: 1.0,
438443
color_mapper: ColorMapper::Function(colormap_to_re_renderer(colormap)),
439444
shader_decoding: None,
@@ -509,7 +514,7 @@ fn segmentation_image_to_gpu(
509514
texture: main_texture_handle,
510515
range: [0.0, (colormap_width * colormap_height) as f32],
511516
decode_srgb: false, // Setting this to true would affect the class ids, not the color they resolve to.
512-
multiply_rgb_with_alpha: false, // already premultiplied!
517+
texture_alpha: TextureAlpha::AlreadyPremultiplied,
513518
gamma: 1.0,
514519
color_mapper: ColorMapper::Texture(colormap_texture_handle),
515520
shader_decoding: None,
@@ -562,37 +567,30 @@ fn general_texture_creation_desc_from_image<'a>(
562567
// BGR->RGB conversion is done in the shader.
563568
ColorModel::RGB | ColorModel::BGR => {
564569
// There are no 3-channel textures in wgpu, so we need to pad to 4 channels.
565-
// What should we pad with? It depends on whether or not the shader interprets these as alpha.
566-
// To be safe, we pad with the MAX value of integers, and with 1.0 for floats.
567-
// TODO(emilk): tell the shader to ignore the alpha channel instead!
570+
// What should we pad with?
571+
// It doesn't matter - we tell the shader to ignore the alpha channel.
568572

569573
match datatype {
570-
ChannelDatatype::U8 => (
571-
pad_rgb_to_rgba(buf, u8::MAX).into(),
572-
TextureFormat::Rgba8Uint,
573-
),
574-
ChannelDatatype::U16 => (pad_cast_img(image, u16::MAX), TextureFormat::Rgba16Uint),
575-
ChannelDatatype::U32 => (pad_cast_img(image, u32::MAX), TextureFormat::Rgba32Uint),
574+
ChannelDatatype::U8 => (pad_rgb_to_rgba(buf, 0).into(), TextureFormat::Rgba8Uint),
575+
ChannelDatatype::U16 => (pad_cast_img::<u16>(image), TextureFormat::Rgba16Uint),
576+
ChannelDatatype::U32 => (pad_cast_img::<u32>(image), TextureFormat::Rgba32Uint),
576577
ChannelDatatype::U64 => (
577-
pad_and_narrow_and_cast(&image.to_slice(), 1.0, |x: u64| x as f32),
578+
pad_and_narrow_and_cast(&image.to_slice(), |x: u64| x as f32),
578579
TextureFormat::Rgba32Float,
579580
),
580581

581-
ChannelDatatype::I8 => (pad_cast_img(image, i8::MAX), TextureFormat::Rgba8Sint),
582-
ChannelDatatype::I16 => (pad_cast_img(image, i16::MAX), TextureFormat::Rgba16Sint),
583-
ChannelDatatype::I32 => (pad_cast_img(image, i32::MAX), TextureFormat::Rgba32Sint),
582+
ChannelDatatype::I8 => (pad_cast_img::<i8>(image), TextureFormat::Rgba8Sint),
583+
ChannelDatatype::I16 => (pad_cast_img::<i16>(image), TextureFormat::Rgba16Sint),
584+
ChannelDatatype::I32 => (pad_cast_img::<i32>(image), TextureFormat::Rgba32Sint),
584585
ChannelDatatype::I64 => (
585-
pad_and_narrow_and_cast(&image.to_slice(), 1.0, |x: i64| x as f32),
586+
pad_and_narrow_and_cast(&image.to_slice(), |x: i64| x as f32),
586587
TextureFormat::Rgba32Float,
587588
),
588589

589-
ChannelDatatype::F16 => (
590-
pad_cast_img(image, half::f16::from_f32(1.0)),
591-
TextureFormat::Rgba16Float,
592-
),
593-
ChannelDatatype::F32 => (pad_cast_img(image, 1.0_f32), TextureFormat::Rgba32Float),
590+
ChannelDatatype::F16 => (pad_cast_img::<f16>(image), TextureFormat::Rgba16Float),
591+
ChannelDatatype::F32 => (pad_cast_img::<f32>(image), TextureFormat::Rgba32Float),
594592
ChannelDatatype::F64 => (
595-
pad_and_narrow_and_cast(&image.to_slice(), 1.0, |x: f64| x as f32),
593+
pad_and_narrow_and_cast(&image.to_slice(), |x: f64| x as f32),
596594
TextureFormat::Rgba32Float,
597595
),
598596
}
@@ -675,29 +673,30 @@ fn narrow_f64_to_f32s(slice: &[f64]) -> Cow<'static, [u8]> {
675673
}
676674

677675
/// Pad an RGB image to RGBA and cast the results to bytes.
678-
fn pad_and_cast<T: Copy + bytemuck::Pod>(data: &[T], pad: T) -> Cow<'static, [u8]> {
676+
fn pad_and_cast<T: Copy + bytemuck::Pod + Default>(data: &[T]) -> Cow<'static, [u8]> {
679677
re_tracing::profile_function!();
680678
// TODO(emilk): optimize by combining the two steps into one; avoiding one allocation and memcpy
681-
let padded: Vec<T> = pad_rgb_to_rgba(data, pad);
679+
let padded: Vec<T> = pad_rgb_to_rgba(data, T::default());
682680
let bytes: Vec<u8> = bytemuck::pod_collect_to_vec(&padded);
683681
bytes.into()
684682
}
685683

686684
/// Pad an RGB image to RGBA and cast the results to bytes.
687-
fn pad_cast_img<T: Copy + bytemuck::Pod>(img: &ImageInfo, pad: T) -> Cow<'static, [u8]> {
688-
pad_and_cast(&img.to_slice(), pad)
685+
fn pad_cast_img<T: Copy + bytemuck::Pod + Default>(img: &ImageInfo) -> Cow<'static, [u8]> {
686+
pad_and_cast::<T>(&img.to_slice())
689687
}
690688

691-
fn pad_and_narrow_and_cast<T: Copy + bytemuck::Pod>(
689+
fn pad_and_narrow_and_cast<T: Copy + bytemuck::Pod + Default>(
692690
data: &[T],
693-
pad: f32,
694691
narrow: impl Fn(T) -> f32,
695692
) -> Cow<'static, [u8]> {
696693
re_tracing::profile_function!();
697694

695+
let alpha = 0.0; // The shader should just ignore it
696+
698697
let floats: Vec<f32> = data
699698
.chunks_exact(3)
700-
.flat_map(|chunk| [narrow(chunk[0]), narrow(chunk[1]), narrow(chunk[2]), pad])
699+
.flat_map(|chunk| [narrow(chunk[0]), narrow(chunk[1]), narrow(chunk[2]), alpha])
701700
.collect();
702701
bytemuck::pod_collect_to_vec(&floats).into()
703702
}

0 commit comments

Comments
 (0)