1use crate::resources::{DualPipeline, ViewportGpuResources};
7use crate::scene::material::ItemSettings;
8use wgpu::util::DeviceExt as _;
9
10#[repr(C)]
28#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
29pub struct ImplicitPrimitive {
30 pub kind: u32,
32 pub blend: f32,
35 #[doc(hidden)]
36 pub _pad: [f32; 2],
37 pub params: [f32; 8],
39 pub colour: [f32; 4],
42}
43
44#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
46pub enum ImplicitBlendMode {
47 #[default]
49 Union,
50 SmoothUnion,
52 Intersection,
54}
55
56#[derive(Clone, Copy, Debug)]
58pub struct GpuImplicitOptions {
59 pub max_steps: u32,
61 pub step_scale: f32,
64 pub hit_threshold: f32,
66 pub max_distance: f32,
68}
69
70impl Default for GpuImplicitOptions {
71 fn default() -> Self {
72 Self {
73 max_steps: 128,
74 step_scale: 0.85,
75 hit_threshold: 5e-4,
76 max_distance: 40.0,
77 }
78 }
79}
80
81#[non_exhaustive]
101pub struct GpuImplicitItem {
102 pub primitives: Vec<ImplicitPrimitive>,
104 pub blend_mode: ImplicitBlendMode,
106 pub march_options: GpuImplicitOptions,
108 pub settings: ItemSettings,
110}
111
112impl Default for GpuImplicitItem {
113 fn default() -> Self {
114 Self {
115 primitives: Vec::new(),
116 blend_mode: crate::resources::ImplicitBlendMode::Union,
117 march_options: GpuImplicitOptions::default(),
118 settings: ItemSettings::default(),
119 }
120 }
121}
122
123impl ImplicitPrimitive {
124 pub fn zeroed() -> Self {
126 bytemuck::Zeroable::zeroed()
127 }
128}
129
130#[repr(C)]
138#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
139pub(crate) struct ImplicitUniformRaw {
140 pub num_primitives: u32,
141 pub blend_mode: u32,
142 pub max_steps: u32,
143 pub unlit: u32,
144 pub step_scale: f32,
145 pub hit_threshold: f32,
146 pub max_distance: f32,
147 pub opacity: f32,
148 pub primitives: [ImplicitPrimitive; 16],
149}
150
151pub(crate) struct ImplicitGpuItem {
153 pub _uniform_buf: wgpu::Buffer,
154 pub bind_group: wgpu::BindGroup,
155}
156
157impl ViewportGpuResources {
162 pub(crate) fn ensure_implicit_pipeline(&mut self, device: &wgpu::Device) {
167 if self.implicit_pipeline.is_some() {
168 return;
169 }
170
171 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
172 label: Some("implicit_shader"),
173 source: wgpu::ShaderSource::Wgsl(
174 include_str!(concat!(env!("OUT_DIR"), "/implicit.wgsl")).into(),
175 ),
176 });
177
178 let implicit_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
180 label: Some("implicit_bgl"),
181 entries: &[wgpu::BindGroupLayoutEntry {
182 binding: 0,
183 visibility: wgpu::ShaderStages::FRAGMENT,
184 ty: wgpu::BindingType::Buffer {
185 ty: wgpu::BufferBindingType::Uniform,
186 has_dynamic_offset: false,
187 min_binding_size: None,
188 },
189 count: None,
190 }],
191 });
192
193 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
195 label: Some("implicit_pipeline_layout"),
196 bind_group_layouts: &[&self.camera_bind_group_layout, &implicit_bgl],
197 push_constant_ranges: &[],
198 });
199
200 let make = |fmt: wgpu::TextureFormat| {
201 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
202 label: Some("implicit_pipeline"),
203 layout: Some(&layout),
204 vertex: wgpu::VertexState {
205 module: &shader,
206 entry_point: Some("vs_main"),
207 buffers: &[],
208 compilation_options: wgpu::PipelineCompilationOptions::default(),
209 },
210 fragment: Some(wgpu::FragmentState {
211 module: &shader,
212 entry_point: Some("fs_main"),
213 targets: &[Some(wgpu::ColorTargetState {
214 format: fmt,
215 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
216 write_mask: wgpu::ColorWrites::ALL,
217 })],
218 compilation_options: wgpu::PipelineCompilationOptions::default(),
219 }),
220 primitive: wgpu::PrimitiveState {
221 topology: wgpu::PrimitiveTopology::TriangleList,
222 cull_mode: None,
223 ..Default::default()
224 },
225 depth_stencil: Some(wgpu::DepthStencilState {
227 format: wgpu::TextureFormat::Depth24PlusStencil8,
228 depth_write_enabled: true,
229 depth_compare: wgpu::CompareFunction::LessEqual,
230 stencil: wgpu::StencilState::default(),
231 bias: wgpu::DepthBiasState::default(),
232 }),
233 multisample: wgpu::MultisampleState {
234 count: 1,
235 mask: !0,
236 alpha_to_coverage_enabled: false,
237 },
238 multiview: None,
239 cache: None,
240 })
241 };
242
243 self.implicit_bgl = Some(implicit_bgl);
244 self.implicit_pipeline = Some(DualPipeline {
245 ldr: make(self.target_format),
246 hdr: make(wgpu::TextureFormat::Rgba16Float),
247 });
248 }
249
250 pub(crate) fn ensure_implicit_outline_mask_pipeline(&mut self, device: &wgpu::Device) {
256 if self.implicit_outline_mask_pipeline.is_some() {
257 return;
258 }
259
260 let implicit_bgl = self.implicit_bgl.as_ref().expect(
261 "ensure_implicit_pipeline must be called before ensure_implicit_outline_mask_pipeline",
262 );
263
264 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
265 label: Some("implicit_outline_mask_shader"),
266 source: wgpu::ShaderSource::Wgsl(
267 include_str!(concat!(env!("OUT_DIR"), "/implicit_outline_mask.wgsl")).into(),
268 ),
269 });
270
271 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
272 label: Some("implicit_outline_mask_pipeline_layout"),
273 bind_group_layouts: &[&self.camera_bind_group_layout, implicit_bgl],
274 push_constant_ranges: &[],
275 });
276
277 self.implicit_outline_mask_pipeline = Some(device.create_render_pipeline(
278 &wgpu::RenderPipelineDescriptor {
279 label: Some("implicit_outline_mask_pipeline"),
280 layout: Some(&layout),
281 vertex: wgpu::VertexState {
282 module: &shader,
283 entry_point: Some("vs_main"),
284 buffers: &[],
285 compilation_options: wgpu::PipelineCompilationOptions::default(),
286 },
287 fragment: Some(wgpu::FragmentState {
288 module: &shader,
289 entry_point: Some("fs_main"),
290 targets: &[Some(wgpu::ColorTargetState {
291 format: wgpu::TextureFormat::R8Unorm,
292 blend: None,
293 write_mask: wgpu::ColorWrites::ALL,
294 })],
295 compilation_options: wgpu::PipelineCompilationOptions::default(),
296 }),
297 primitive: wgpu::PrimitiveState {
298 topology: wgpu::PrimitiveTopology::TriangleList,
299 cull_mode: None,
300 ..Default::default()
301 },
302 depth_stencil: Some(wgpu::DepthStencilState {
303 format: wgpu::TextureFormat::Depth24PlusStencil8,
304 depth_write_enabled: true,
305 depth_compare: wgpu::CompareFunction::Less,
306 stencil: wgpu::StencilState::default(),
307 bias: wgpu::DepthBiasState::default(),
308 }),
309 multisample: wgpu::MultisampleState {
310 count: 1,
311 mask: !0,
312 alpha_to_coverage_enabled: false,
313 },
314 multiview: None,
315 cache: None,
316 },
317 ));
318 }
319
320 pub(crate) fn upload_implicit_item(
324 &self,
325 device: &wgpu::Device,
326 item: &GpuImplicitItem,
327 ) -> ImplicitGpuItem {
328 let blend_mode_u32 = match item.blend_mode {
330 ImplicitBlendMode::Union => 0u32,
331 ImplicitBlendMode::SmoothUnion => 1,
332 ImplicitBlendMode::Intersection => 2,
333 };
334
335 let mut raw = ImplicitUniformRaw {
336 num_primitives: item.primitives.len().min(16) as u32,
337 blend_mode: blend_mode_u32,
338 max_steps: item.march_options.max_steps,
339 unlit: item.settings.unlit as u32,
340 step_scale: item.march_options.step_scale,
341 hit_threshold: item.march_options.hit_threshold,
342 max_distance: item.march_options.max_distance,
343 opacity: item.settings.opacity,
344 primitives: [ImplicitPrimitive::zeroed(); 16],
345 };
346
347 for (i, prim) in item.primitives.iter().take(16).enumerate() {
348 raw.primitives[i] = *prim;
349 }
350
351 let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
352 label: Some("implicit_uniform_buf"),
353 contents: bytemuck::bytes_of(&raw),
354 usage: wgpu::BufferUsages::UNIFORM,
355 });
356
357 let bgl = self
358 .implicit_bgl
359 .as_ref()
360 .expect("ensure_implicit_pipeline not called");
361
362 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
363 label: Some("implicit_bind_group"),
364 layout: bgl,
365 entries: &[wgpu::BindGroupEntry {
366 binding: 0,
367 resource: uniform_buf.as_entire_binding(),
368 }],
369 });
370
371 ImplicitGpuItem {
372 _uniform_buf: uniform_buf,
373 bind_group,
374 }
375 }
376}