use smallvec::smallvec;
use crate::allocator::create_and_fill_uniform_buffer_batch;
use crate::device_caps::DeviceCapabilityTier;
use crate::renderer::screen_triangle_vertex_shader;
use crate::view_builder::ViewBuilder;
use crate::wgpu_resources::{
BindGroupDesc, BindGroupEntry, BindGroupLayoutDesc, GpuBindGroup, GpuBindGroupLayoutHandle,
GpuRenderPipelineHandle, GpuRenderPipelinePoolAccessor, GpuTexture, PipelineLayoutDesc,
PoolError, RenderPipelineDesc, SamplerDesc,
};
use crate::{DebugLabel, RenderContext, include_shader_module};
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
pub struct OutlineMaskPreference(pub Option<[u8; 2]>);
impl OutlineMaskPreference {
pub const NONE: Self = Self(None);
#[inline]
pub fn some(channel_a: u8, channel_b: u8) -> Self {
Self(Some([channel_a, channel_b]))
}
#[inline]
pub fn is_some(self) -> bool {
self.0.is_some()
}
#[inline]
pub fn is_none(self) -> bool {
self.0.is_none()
}
#[inline]
pub fn with_fallback_to(self, other: Self) -> Self {
if let Some([a, b]) = self.0 {
if let Some([other_a, other_b]) = other.0 {
Self::some(
if a == 0 { other_a } else { a },
if b == 0 { other_b } else { b },
)
} else {
self
}
} else {
other
}
}
}
#[derive(Clone, Debug)]
pub struct OutlineConfig {
pub outline_radius_pixel: f32,
pub color_layer_a: crate::Rgba,
pub color_layer_b: crate::Rgba,
}
pub struct OutlineMaskProcessor {
label: DebugLabel,
mask_texture: GpuTexture,
mask_depth: GpuTexture,
voronoi_textures: [GpuTexture; 2],
bind_group_jumpflooding_init: GpuBindGroup,
bind_group_jumpflooding_steps: Vec<GpuBindGroup>,
render_pipeline_jumpflooding_init: GpuRenderPipelineHandle,
render_pipeline_jumpflooding_step: GpuRenderPipelineHandle,
}
mod gpu_data {
use crate::wgpu_buffer_types;
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct JumpfloodingStepUniformBuffer {
pub step_width: wgpu_buffer_types::U32RowPadded,
pub end_padding: [wgpu_buffer_types::PaddingRow; 16 - 1],
}
}
impl OutlineMaskProcessor {
pub const MASK_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rg8Uint;
pub const MASK_DEPTH_FORMAT: wgpu::TextureFormat = ViewBuilder::MAIN_TARGET_DEPTH_FORMAT;
pub const MASK_DEPTH_STATE: Option<wgpu::DepthStencilState> = Some(wgpu::DepthStencilState {
format: Self::MASK_DEPTH_FORMAT,
depth_compare: wgpu::CompareFunction::GreaterEqual,
depth_write_enabled: true,
stencil: wgpu::StencilState {
front: wgpu::StencilFaceState::IGNORE,
back: wgpu::StencilFaceState::IGNORE,
read_mask: 0,
write_mask: 0,
},
bias: wgpu::DepthBiasState {
constant: 0,
slope_scale: 0.0,
clamp: 0.0,
},
});
const VORONOI_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float;
pub fn mask_default_msaa_state(tier: DeviceCapabilityTier) -> wgpu::MultisampleState {
wgpu::MultisampleState {
count: Self::mask_sample_count(tier),
mask: !0,
alpha_to_coverage_enabled: false,
}
}
pub fn mask_sample_count(tier: DeviceCapabilityTier) -> u32 {
if tier.support_sampling_msaa_texture() {
4
} else {
1
}
}
pub fn new(
ctx: &RenderContext,
config: &OutlineConfig,
view_name: &DebugLabel,
resolution_in_pixel: [u32; 2],
) -> Self {
re_tracing::profile_function!();
let instance_label: DebugLabel = format!("{view_name} - OutlineMaskProcessor").into();
let texture_pool = &ctx.gpu_resources.textures;
let mask_sample_count = Self::mask_sample_count(ctx.device_caps().tier);
let mask_texture_desc = crate::wgpu_resources::TextureDesc {
label: format!("{instance_label}::mask_texture").into(),
size: wgpu::Extent3d {
width: resolution_in_pixel[0],
height: resolution_in_pixel[1],
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: mask_sample_count,
dimension: wgpu::TextureDimension::D2,
format: Self::MASK_FORMAT,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
};
let mask_texture = texture_pool.alloc(&ctx.device, &mask_texture_desc);
let mask_depth = texture_pool.alloc(
&ctx.device,
&crate::wgpu_resources::TextureDesc {
label: format!("{instance_label}::mask_depth").into(),
format: Self::MASK_DEPTH_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
..mask_texture_desc
},
);
let voronoi_texture_desc = crate::wgpu_resources::TextureDesc {
label: format!("{instance_label}::distance_texture").into(),
sample_count: 1,
format: Self::VORONOI_FORMAT,
..mask_texture_desc
};
let voronoi_textures = [
texture_pool.alloc(&ctx.device, &voronoi_texture_desc.with_label_push("0")),
texture_pool.alloc(&ctx.device, &voronoi_texture_desc.with_label_push("1")),
];
let (bind_group_jumpflooding_init, bind_group_layout_jumpflooding_init) =
Self::create_bind_group_jumpflooding_init(ctx, &instance_label, &mask_texture);
let (bind_group_jumpflooding_steps, bind_group_layout_jumpflooding_step) =
Self::create_bind_groups_for_jumpflooding_steps(
config,
ctx,
&instance_label,
&voronoi_textures,
);
let screen_triangle_vertex_shader = screen_triangle_vertex_shader(ctx);
let jumpflooding_init_shader_module = if mask_sample_count == 1 {
include_shader_module!("../../shader/outlines/jumpflooding_init.wgsl")
} else {
include_shader_module!("../../shader/outlines/jumpflooding_init_msaa.wgsl")
};
let jumpflooding_init_desc = RenderPipelineDesc {
label: "OutlineMaskProcessor::jumpflooding_init".into(),
pipeline_layout: ctx.gpu_resources.pipeline_layouts.get_or_create(
ctx,
&PipelineLayoutDesc {
label: "OutlineMaskProcessor::jumpflooding_init".into(),
entries: vec![bind_group_layout_jumpflooding_init],
},
),
vertex_entrypoint: "main".into(),
vertex_handle: screen_triangle_vertex_shader,
fragment_entrypoint: "main".into(),
fragment_handle: ctx
.gpu_resources
.shader_modules
.get_or_create(ctx, &jumpflooding_init_shader_module),
vertex_buffers: smallvec![],
render_targets: smallvec![Some(Self::VORONOI_FORMAT.into())],
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
};
let render_pipeline_jumpflooding_init = ctx
.gpu_resources
.render_pipelines
.get_or_create(ctx, &jumpflooding_init_desc);
let render_pipeline_jumpflooding_step = ctx.gpu_resources.render_pipelines.get_or_create(
ctx,
&RenderPipelineDesc {
label: "OutlineMaskProcessor::jumpflooding_step".into(),
pipeline_layout: ctx.gpu_resources.pipeline_layouts.get_or_create(
ctx,
&PipelineLayoutDesc {
label: "OutlineMaskProcessor::jumpflooding_step".into(),
entries: vec![bind_group_layout_jumpflooding_step],
},
),
fragment_handle: ctx.gpu_resources.shader_modules.get_or_create(
ctx,
&include_shader_module!("../../shader/outlines/jumpflooding_step.wgsl"),
),
..jumpflooding_init_desc
},
);
Self {
label: instance_label,
mask_texture,
mask_depth,
voronoi_textures,
bind_group_jumpflooding_init,
bind_group_jumpflooding_steps,
render_pipeline_jumpflooding_init,
render_pipeline_jumpflooding_step,
}
}
pub fn final_voronoi_texture(&self) -> &GpuTexture {
&self.voronoi_textures[self.bind_group_jumpflooding_steps.len() % 2]
}
pub fn start_mask_render_pass<'a>(
&'a self,
encoder: &'a mut wgpu::CommandEncoder,
) -> wgpu::RenderPass<'a> {
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: DebugLabel::from(format!("{} - mask pass", self.label)).get(),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &self.mask_texture.default_view,
depth_slice: None,
resolve_target: None, ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &self.mask_depth.default_view,
depth_ops: Some(wgpu::Operations {
load: ViewBuilder::DEFAULT_DEPTH_CLEAR,
store: wgpu::StoreOp::Discard,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
})
}
pub fn compute_outlines(
&self,
pipelines: &GpuRenderPipelinePoolAccessor<'_>,
encoder: &mut wgpu::CommandEncoder,
) -> Result<(), PoolError> {
let ops = wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store,
};
{
let mut jumpflooding_init = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: DebugLabel::from(format!("{} - jumpflooding_init", self.label)).get(),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &self.voronoi_textures[0].default_view,
depth_slice: None,
resolve_target: None,
ops,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
let render_pipeline_init = pipelines.get(self.render_pipeline_jumpflooding_init)?;
jumpflooding_init.set_bind_group(0, &self.bind_group_jumpflooding_init, &[]);
jumpflooding_init.set_pipeline(render_pipeline_init);
jumpflooding_init.draw(0..3, 0..1);
}
let render_pipeline_step = pipelines.get(self.render_pipeline_jumpflooding_step)?;
for (i, bind_group) in self.bind_group_jumpflooding_steps.iter().enumerate() {
let mut jumpflooding_step = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: DebugLabel::from(format!("{} - jumpflooding_step {i}", self.label)).get(),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &self.voronoi_textures[(i + 1) % 2].default_view,
depth_slice: None,
resolve_target: None,
ops,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
jumpflooding_step.set_pipeline(render_pipeline_step);
jumpflooding_step.set_bind_group(0, bind_group, &[]);
jumpflooding_step.draw(0..3, 0..1);
}
Ok(())
}
fn create_bind_group_jumpflooding_init(
ctx: &RenderContext,
instance_label: &DebugLabel,
mask_texture: &GpuTexture,
) -> (GpuBindGroup, GpuBindGroupLayoutHandle) {
let bind_group_layout_jumpflooding_init =
ctx.gpu_resources.bind_group_layouts.get_or_create(
&ctx.device,
&BindGroupLayoutDesc {
label: "OutlineMaskProcessor::bind_group_layout_jumpflooding_init".into(),
entries: vec![wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Uint,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: mask_texture.texture.sample_count() > 1,
},
count: None,
}],
},
);
(
ctx.gpu_resources.bind_groups.alloc(
&ctx.device,
&ctx.gpu_resources,
&BindGroupDesc {
label: format!("{instance_label}::jumpflooding_init").into(),
entries: smallvec![BindGroupEntry::DefaultTextureView(mask_texture.handle)],
layout: bind_group_layout_jumpflooding_init,
},
),
bind_group_layout_jumpflooding_init,
)
}
fn create_bind_groups_for_jumpflooding_steps(
config: &OutlineConfig,
ctx: &RenderContext,
instance_label: &DebugLabel,
voronoi_textures: &[GpuTexture; 2],
) -> (Vec<GpuBindGroup>, GpuBindGroupLayoutHandle) {
let bind_group_layout_jumpflooding_step =
ctx.gpu_resources.bind_group_layouts.get_or_create(
&ctx.device,
&BindGroupLayoutDesc {
label: "OutlineMaskProcessor::bind_group_layout_jumpflooding_step".into(),
entries: vec![
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,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: std::num::NonZeroU64::new(std::mem::size_of::<
gpu_data::JumpfloodingStepUniformBuffer,
>(
)
as _),
},
count: None,
},
],
},
);
let max_step_width =
(config.outline_radius_pixel.max(1.0).ceil() as u32).next_power_of_two();
let num_steps = max_step_width.ilog2() + 1;
let uniform_buffer_jumpflooding_steps_bindings = create_and_fill_uniform_buffer_batch(
ctx,
"jumpflooding uniformbuffer".into(),
(0..num_steps).map(|step| gpu_data::JumpfloodingStepUniformBuffer {
step_width: (max_step_width >> step).into(),
end_padding: Default::default(),
}),
);
let sampler = ctx.gpu_resources.samplers.get_or_create(
&ctx.device,
&SamplerDesc {
label: "nearest_clamp".into(),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
..Default::default()
},
);
let uniform_buffer_jumpflooding_steps = uniform_buffer_jumpflooding_steps_bindings
.into_iter()
.enumerate()
.map(|(i, uniform_buffer_binding)| {
ctx.gpu_resources.bind_groups.alloc(
&ctx.device,
&ctx.gpu_resources,
&BindGroupDesc {
label: format!("{instance_label}::jumpflooding_steps[{i}]").into(),
entries: smallvec![
BindGroupEntry::DefaultTextureView(voronoi_textures[i % 2].handle),
BindGroupEntry::Sampler(sampler),
uniform_buffer_binding
],
layout: bind_group_layout_jumpflooding_step,
},
)
})
.collect();
(
uniform_buffer_jumpflooding_steps,
bind_group_layout_jumpflooding_step,
)
}
}