1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//! Heatmap colour-mapping pipeline (Pass 2).
//!
//! Reads the accumulated R16Float heatmap weight texture and maps it
//! through a 1-D colour ramp, compositing the result onto the main
//! surface with alpha blending.
use crate::pipeline::uniforms::ViewProjUniform;
/// The heatmap colour-mapping pipeline (Pass 2).
pub struct HeatmapColormapPipeline {
/// Render pipeline for the fullscreen colour-mapping pass.
pub pipeline: wgpu::RenderPipeline,
/// Bind group layout for the shared view-projection uniform (group 0).
pub uniform_bind_group_layout: wgpu::BindGroupLayout,
/// Bind group layout for the heat texture + ramp texture (group 1).
pub textures_bind_group_layout: wgpu::BindGroupLayout,
}
impl HeatmapColormapPipeline {
/// Create the colour-mapping pipeline.
///
/// `surface_format` must match the swap-chain / surface texture format.
pub fn new(device: &wgpu::Device, surface_format: wgpu::TextureFormat) -> Self {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("heatmap_colormap_shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../shaders/heatmap_colormap.wgsl").into(),
),
});
// Group 0 — shared view-projection uniform.
let uniform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("heatmap_colormap_uniform_bgl"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: wgpu::BufferSize::new(
std::mem::size_of::<ViewProjUniform>() as u64,
),
},
count: None,
}],
});
// Group 1 — heat accumulation texture + colour ramp texture + sampler.
let textures_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("heatmap_colormap_textures_bgl"),
entries: &[
// binding 0: heat accumulation texture (R16Float, sampled
// via textureLoad).
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
// binding 1: 256×1 colour ramp texture (Rgba8Unorm).
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
// binding 2: linear sampler for the colour ramp.
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("heatmap_colormap_pipeline_layout"),
bind_group_layouts: &[&uniform_bind_group_layout, &textures_bind_group_layout],
push_constant_ranges: &[],
});
// Pre-multiplied alpha blending for compositing over existing scene.
let alpha_blend = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
};
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("heatmap_colormap_pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[], // fullscreen triangle from vertex_index
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: surface_format,
blend: Some(alpha_blend),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
..Default::default()
},
depth_stencil: None, // no depth for fullscreen composite
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
Self {
pipeline,
uniform_bind_group_layout,
textures_bind_group_layout,
}
}
}