Skip to content

Commit 437fe2b

Browse files
authored
GPU colormapping, first step (#1835)
* Add TextureManager2D::get_or_create_with * Small code cleanup * Add code to upload a Tensor to a GPU texture * Add helper method Tensor::image_height_width_depth * Minor code cleanup (multiplicative_tint) * Hook up color textures via the new path * Refactor: introduce ColormappedTexture * Start working on an uint sampler * merge fix * Dumb colormapping of depth textures! * Use turbo for depth maps (and single-channel images :grimace:) * Use grayscale for luminance * ColorMap -> Colormap * Apply annotation context colormaps * Support sint textures too * cleanup * merge fix * Fix RGB images * More cleanup * Better error-handlign and nicer error message * Clean up the SAMPLE_TYPE with constants * Nicer shader interface * Better error handling * Remove dead code * Self-review cleanup * Fix bug in shader when sampling sint textures * Use textureSampleLevel * Apply a gamma to the image (unused as of now) * image_height_width_channels * fix various review comments * Optimize narrow_f64_to_f32s: avoid one allocation
1 parent 48d4f28 commit 437fe2b

File tree

23 files changed

+1055
-224
lines changed

23 files changed

+1055
-224
lines changed

crates/re_data_store/src/entity_properties.rs

+12-11
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ impl ExtraQueryHistory {
140140

141141
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
142142
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
143-
pub enum ColorMap {
143+
pub enum Colormap {
144+
/// Perceptually even
144145
Grayscale,
145146
#[default]
146147
Turbo,
@@ -150,15 +151,15 @@ pub enum ColorMap {
150151
Inferno,
151152
}
152153

153-
impl std::fmt::Display for ColorMap {
154+
impl std::fmt::Display for Colormap {
154155
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155156
f.write_str(match self {
156-
ColorMap::Grayscale => "Grayscale",
157-
ColorMap::Turbo => "Turbo",
158-
ColorMap::Viridis => "Viridis",
159-
ColorMap::Plasma => "Plasma",
160-
ColorMap::Magma => "Magma",
161-
ColorMap::Inferno => "Inferno",
157+
Colormap::Grayscale => "Grayscale",
158+
Colormap::Turbo => "Turbo",
159+
Colormap::Viridis => "Viridis",
160+
Colormap::Plasma => "Plasma",
161+
Colormap::Magma => "Magma",
162+
Colormap::Inferno => "Inferno",
162163
})
163164
}
164165
}
@@ -167,23 +168,23 @@ impl std::fmt::Display for ColorMap {
167168
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
168169
pub enum ColorMapper {
169170
/// Use a well-known color map, pre-implemented as a wgsl module.
170-
ColorMap(ColorMap),
171+
Colormap(Colormap),
171172
// TODO(cmc): support textures.
172173
// TODO(cmc): support custom transfer functions.
173174
}
174175

175176
impl std::fmt::Display for ColorMapper {
176177
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177178
match self {
178-
ColorMapper::ColorMap(colormap) => colormap.fmt(f),
179+
ColorMapper::Colormap(colormap) => colormap.fmt(f),
179180
}
180181
}
181182
}
182183

183184
impl Default for ColorMapper {
184185
#[inline]
185186
fn default() -> Self {
186-
Self::ColorMap(ColorMap::default())
187+
Self::Colormap(Colormap::default())
187188
}
188189
}
189190

crates/re_log_types/src/component_types/tensor.rs

+17
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,23 @@ impl Tensor {
369369
self.shape.len()
370370
}
371371

372+
/// If this tensor is shaped as an image, return the height, width, and channels/depth of it.
373+
pub fn image_height_width_channels(&self) -> Option<[u64; 3]> {
374+
if self.shape.len() == 2 {
375+
Some([self.shape[0].size, self.shape[1].size, 1])
376+
} else if self.shape.len() == 3 {
377+
let channels = self.shape[2].size;
378+
// gray, rgb, rgba
379+
if matches!(channels, 1 | 3 | 4) {
380+
Some([self.shape[0].size, self.shape[1].size, channels])
381+
} else {
382+
None
383+
}
384+
} else {
385+
None
386+
}
387+
}
388+
372389
pub fn is_shaped_like_an_image(&self) -> bool {
373390
self.num_dim() == 2
374391
|| self.num_dim() == 3 && {

crates/re_renderer/examples/2d.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use ecolor::Hsva;
22
use re_renderer::{
33
renderer::{
4-
LineStripFlags, RectangleDrawData, TextureFilterMag, TextureFilterMin, TexturedRect,
4+
ColormappedTexture, LineStripFlags, RectangleDrawData, TextureFilterMag, TextureFilterMin,
5+
TexturedRect,
56
},
67
resource_managers::{GpuTexture2DHandle, Texture2DCreationDesc},
78
view_builder::{self, Projection, TargetConfiguration, ViewBuilder},
@@ -39,7 +40,7 @@ impl framework::Example for Render2D {
3940
&mut re_ctx.gpu_resources.textures,
4041
&Texture2DCreationDesc {
4142
label: "rerun logo".into(),
42-
data: &image_data,
43+
data: image_data.into(),
4344
format: wgpu::TextureFormat::Rgba8UnormSrgb,
4445
width: rerun_logo.width(),
4546
height: rerun_logo.height(),
@@ -193,7 +194,9 @@ impl framework::Example for Render2D {
193194
top_left_corner_position: glam::vec3(500.0, 120.0, -0.05),
194195
extent_u: self.rerun_logo_texture_width as f32 * image_scale * glam::Vec3::X,
195196
extent_v: self.rerun_logo_texture_height as f32 * image_scale * glam::Vec3::Y,
196-
texture: self.rerun_logo_texture.clone(),
197+
colormapped_texture: ColormappedTexture::from_unorm_srgba(
198+
self.rerun_logo_texture.clone(),
199+
),
197200
texture_filter_magnification: TextureFilterMag::Nearest,
198201
texture_filter_minification: TextureFilterMin::Linear,
199202
..Default::default()
@@ -207,7 +210,9 @@ impl framework::Example for Render2D {
207210
),
208211
extent_u: self.rerun_logo_texture_width as f32 * image_scale * glam::Vec3::X,
209212
extent_v: self.rerun_logo_texture_height as f32 * image_scale * glam::Vec3::Y,
210-
texture: self.rerun_logo_texture.clone(),
213+
colormapped_texture: ColormappedTexture::from_unorm_srgba(
214+
self.rerun_logo_texture.clone(),
215+
),
211216
texture_filter_magnification: TextureFilterMag::Linear,
212217
texture_filter_minification: TextureFilterMin::Linear,
213218
depth_offset: 1,

crates/re_renderer/examples/depth_cloud.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ use itertools::Itertools;
2020
use macaw::IsoTransform;
2121
use re_renderer::{
2222
renderer::{
23-
DepthCloud, DepthCloudDepthData, DepthCloudDrawData, DepthClouds, DrawData,
24-
GenericSkyboxDrawData, RectangleDrawData, TexturedRect,
23+
ColormappedTexture, DepthCloud, DepthCloudDepthData, DepthCloudDrawData, DepthClouds,
24+
DrawData, GenericSkyboxDrawData, RectangleDrawData, TexturedRect,
2525
},
2626
resource_managers::{GpuTexture2DHandle, Texture2DCreationDesc},
2727
view_builder::{self, Projection, ViewBuilder},
@@ -183,7 +183,7 @@ impl RenderDepthClouds {
183183
max_depth_in_world: 5.0,
184184
depth_dimensions: depth.dimensions,
185185
depth_data: depth.data.clone(),
186-
colormap: re_renderer::ColorMap::ColorMapTurbo,
186+
colormap: re_renderer::Colormap::Turbo,
187187
outline_mask_id: Default::default(),
188188
}],
189189
radius_boost_in_ui_points_for_outlines: 2.5,
@@ -245,7 +245,7 @@ impl framework::Example for RenderDepthClouds {
245245
&mut re_ctx.gpu_resources.textures,
246246
&Texture2DCreationDesc {
247247
label: "albedo".into(),
248-
data: bytemuck::cast_slice(&albedo.rgba8),
248+
data: bytemuck::cast_slice(&albedo.rgba8).into(),
249249
format: wgpu::TextureFormat::Rgba8UnormSrgb,
250250
width: albedo.dimensions.x,
251251
height: albedo.dimensions.y,
@@ -331,7 +331,7 @@ impl framework::Example for RenderDepthClouds {
331331
.transform_point3(glam::Vec3::new(1.0, 1.0, 0.0)),
332332
extent_u: world_from_model.transform_vector3(-glam::Vec3::X),
333333
extent_v: world_from_model.transform_vector3(-glam::Vec3::Y),
334-
texture: albedo_handle.clone(),
334+
colormapped_texture: ColormappedTexture::from_unorm_srgba(albedo_handle.clone()),
335335
texture_filter_magnification: re_renderer::renderer::TextureFilterMag::Nearest,
336336
texture_filter_minification: re_renderer::renderer::TextureFilterMin::Linear,
337337
multiplicative_tint: Rgba::from_white_alpha(0.5),

crates/re_renderer/shader/colormap.wgsl

+11-9
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@
22
#import <./utils/srgb.wgsl>
33

44
// NOTE: Keep in sync with `colormap.rs`!
5-
const GRAYSCALE: u32 = 0u;
6-
const COLORMAP_TURBO: u32 = 1u;
7-
const COLORMAP_VIRIDIS: u32 = 2u;
8-
const COLORMAP_PLASMA: u32 = 3u;
9-
const COLORMAP_MAGMA: u32 = 4u;
10-
const COLORMAP_INFERNO: u32 = 5u;
5+
const COLORMAP_GRAYSCALE: u32 = 1u;
6+
const COLORMAP_TURBO: u32 = 2u;
7+
const COLORMAP_VIRIDIS: u32 = 3u;
8+
const COLORMAP_PLASMA: u32 = 4u;
9+
const COLORMAP_MAGMA: u32 = 5u;
10+
const COLORMAP_INFERNO: u32 = 6u;
1111

1212
/// Returns a gamma-space sRGB in 0-1 range.
1313
///
1414
/// The input will be saturated to [0, 1] range.
1515
fn colormap_srgb(which: u32, t: f32) -> Vec3 {
16-
if which == COLORMAP_TURBO {
16+
if which == COLORMAP_GRAYSCALE {
17+
return linear_from_srgb(Vec3(t));
18+
} else if which == COLORMAP_TURBO {
1719
return colormap_turbo_srgb(t);
1820
} else if which == COLORMAP_VIRIDIS {
1921
return colormap_viridis_srgb(t);
@@ -23,8 +25,8 @@ fn colormap_srgb(which: u32, t: f32) -> Vec3 {
2325
return colormap_magma_srgb(t);
2426
} else if which == COLORMAP_INFERNO {
2527
return colormap_inferno_srgb(t);
26-
} else { // assume grayscale
27-
return linear_from_srgb(Vec3(t));
28+
} else {
29+
return ERROR_RGBA.rgb;
2830
}
2931
}
3032

crates/re_renderer/shader/rectangle.wgsl

+80-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
11
#import <./types.wgsl>
2+
#import <./colormap.wgsl>
23
#import <./global_bindings.wgsl>
34
#import <./utils/depth_offset.wgsl>
45

6+
// Keep in sync with mirror in rectangle.rs
7+
8+
// Which texture to read from?
9+
const SAMPLE_TYPE_FLOAT = 1u;
10+
const SAMPLE_TYPE_SINT = 2u;
11+
const SAMPLE_TYPE_UINT = 3u;
12+
13+
// How do we do colormapping?
14+
const COLOR_MAPPER_OFF = 1u;
15+
const COLOR_MAPPER_FUNCTION = 2u;
16+
const COLOR_MAPPER_TEXTURE = 3u;
17+
518
struct UniformBuffer {
619
/// Top left corner position in world space.
720
top_left_corner_position: Vec3,
821

22+
/// Which colormap to use, if any
23+
colormap_function: u32,
24+
925
/// Vector that spans up the rectangle from its top left corner along the u axis of the texture.
1026
extent_u: Vec3,
1127

28+
/// Which texture sample to use
29+
sample_type: u32,
30+
1231
/// Vector that spans up the rectangle from its top left corner along the v axis of the texture.
1332
extent_v: Vec3,
1433

@@ -18,16 +37,35 @@ struct UniformBuffer {
1837
multiplicative_tint: Vec4,
1938

2039
outline_mask: UVec2,
40+
41+
/// Range of the texture values.
42+
/// Will be mapped to the [0, 1] range before we colormap.
43+
range_min_max: Vec2,
44+
45+
color_mapper: u32,
46+
47+
/// Exponent to raise the normalized texture value.
48+
/// Inverse brightness.
49+
gamma: f32,
2150
};
2251

2352
@group(1) @binding(0)
2453
var<uniform> rect_info: UniformBuffer;
2554

2655
@group(1) @binding(1)
27-
var texture: texture_2d<f32>;
56+
var texture_sampler: sampler;
2857

2958
@group(1) @binding(2)
30-
var texture_sampler: sampler;
59+
var texture_float: texture_2d<f32>;
60+
61+
@group(1) @binding(3)
62+
var texture_sint: texture_2d<i32>;
63+
64+
@group(1) @binding(4)
65+
var texture_uint: texture_2d<u32>;
66+
67+
@group(1) @binding(5)
68+
var colormap_texture: texture_2d<f32>;
3169

3270

3371
struct VertexOut {
@@ -50,7 +88,46 @@ fn vs_main(@builtin(vertex_index) v_idx: u32) -> VertexOut {
5088

5189
@fragment
5290
fn fs_main(in: VertexOut) -> @location(0) Vec4 {
53-
let texture_color = textureSample(texture, texture_sampler, in.texcoord);
91+
// Sample the main texture:
92+
var sampled_value: Vec4;
93+
if rect_info.sample_type == SAMPLE_TYPE_FLOAT {
94+
sampled_value = textureSampleLevel(texture_float, texture_sampler, in.texcoord, 0.0); // TODO(emilk): support mipmaps
95+
} else if rect_info.sample_type == SAMPLE_TYPE_SINT {
96+
let icoords = IVec2(in.texcoord * Vec2(textureDimensions(texture_sint).xy));
97+
sampled_value = Vec4(textureLoad(texture_sint, icoords, 0));
98+
} else if rect_info.sample_type == SAMPLE_TYPE_UINT {
99+
let icoords = IVec2(in.texcoord * Vec2(textureDimensions(texture_uint).xy));
100+
sampled_value = Vec4(textureLoad(texture_uint, icoords, 0));
101+
} else {
102+
return ERROR_RGBA; // unknown sample type
103+
}
104+
105+
// Normalize the sample:
106+
let range = rect_info.range_min_max;
107+
var normalized_value: Vec4 = (sampled_value - range.x) / (range.y - range.x);
108+
109+
// Apply gamma:
110+
normalized_value = vec4(pow(normalized_value.rgb, vec3(rect_info.gamma)), normalized_value.a); // TODO(emilk): handle premultiplied alpha
111+
112+
// Apply colormap, if any:
113+
var texture_color: Vec4;
114+
if rect_info.color_mapper == COLOR_MAPPER_OFF {
115+
texture_color = normalized_value;
116+
} else if rect_info.color_mapper == COLOR_MAPPER_FUNCTION {
117+
let rgb = colormap_linear(rect_info.colormap_function, normalized_value.r);
118+
texture_color = Vec4(rgb, 1.0);
119+
} else if rect_info.color_mapper == COLOR_MAPPER_TEXTURE {
120+
let colormap_size = textureDimensions(colormap_texture).xy;
121+
let color_index = normalized_value.r * f32(colormap_size.x * colormap_size.y);
122+
// TODO(emilk): interpolate between neighboring colors for non-integral color indices
123+
let color_index_i32 = i32(color_index);
124+
let x = color_index_i32 % colormap_size.x;
125+
let y = color_index_i32 / colormap_size.x;
126+
texture_color = textureLoad(colormap_texture, IVec2(x, y), 0);
127+
} else {
128+
return ERROR_RGBA; // unknown color mapper
129+
}
130+
54131
return texture_color * rect_info.multiplicative_tint;
55132
}
56133

crates/re_renderer/shader/types.wgsl

+4
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,7 @@ const ONE = Vec4(1.0, 1.0, 1.0, 1.0);
4848
// fn inf() -> f32 {
4949
// return 1.0 / 0.0;
5050
// }
51+
52+
53+
/// The color to use when we encounter an error.
54+
const ERROR_RGBA = Vec4(1.0, 0.0, 1.0, 1.0);

crates/re_renderer/src/colormap.rs

+18-15
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,28 @@ use glam::{Vec2, Vec3A, Vec4, Vec4Swizzles};
55
// ---
66

77
// NOTE: Keep in sync with `colormap.wgsl`!
8-
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
8+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
99
#[repr(u32)]
10-
pub enum ColorMap {
11-
Grayscale = 0,
12-
ColorMapTurbo = 1,
13-
ColorMapViridis = 2,
14-
ColorMapPlasma = 3,
15-
ColorMapMagma = 4,
16-
ColorMapInferno = 5,
10+
pub enum Colormap {
11+
// Reserve 0 for "disabled"
12+
/// Perceptually even
13+
#[default]
14+
Grayscale = 1,
15+
Turbo = 2,
16+
Viridis = 3,
17+
Plasma = 4,
18+
Magma = 5,
19+
Inferno = 6,
1720
}
1821

19-
pub fn colormap_srgb(which: ColorMap, t: f32) -> [u8; 4] {
22+
pub fn colormap_srgb(which: Colormap, t: f32) -> [u8; 4] {
2023
match which {
21-
ColorMap::Grayscale => grayscale_srgb(t),
22-
ColorMap::ColorMapTurbo => colormap_turbo_srgb(t),
23-
ColorMap::ColorMapViridis => colormap_viridis_srgb(t),
24-
ColorMap::ColorMapPlasma => colormap_plasma_srgb(t),
25-
ColorMap::ColorMapMagma => colormap_magma_srgb(t),
26-
ColorMap::ColorMapInferno => colormap_inferno_srgb(t),
24+
Colormap::Grayscale => grayscale_srgb(t),
25+
Colormap::Turbo => colormap_turbo_srgb(t),
26+
Colormap::Viridis => colormap_viridis_srgb(t),
27+
Colormap::Plasma => colormap_plasma_srgb(t),
28+
Colormap::Magma => colormap_magma_srgb(t),
29+
Colormap::Inferno => colormap_inferno_srgb(t),
2730
}
2831
}
2932

crates/re_renderer/src/importer/gltf.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ pub fn load_gltf_from_buffer(
6262
format!("gltf image used by {texture_names} in {mesh_name}")
6363
}
6464
.into(),
65-
data: &data,
65+
data: data.into(),
6666
format,
6767
width: image.width,
6868
height: image.height,

crates/re_renderer/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub use allocator::GpuReadbackIdentifier;
3333
pub use color::Rgba32Unmul;
3434
pub use colormap::{
3535
colormap_inferno_srgb, colormap_magma_srgb, colormap_plasma_srgb, colormap_srgb,
36-
colormap_turbo_srgb, colormap_viridis_srgb, grayscale_srgb, ColorMap,
36+
colormap_turbo_srgb, colormap_viridis_srgb, grayscale_srgb, Colormap,
3737
};
3838
pub use context::RenderContext;
3939
pub use debug_label::DebugLabel;

0 commit comments

Comments
 (0)