1use crate::{
2 mesh_material::{
3 MeshMaterialBindGroup, MeshMaterialBindGroupLayout, MeshMaterialSystems,
4 TextureBindGroupLayout,
5 },
6 prepass::{DeferredBindGroup, PrepassBindGroup, PrepassPipeline, PrepassTextures},
7 view::{FrameCounter, FrameUniform, PreviousViewUniformOffset},
8 HikariSettings, NoiseTextures, LIGHT_SHADER_HANDLE, WORKGROUP_SIZE,
9};
10use bevy::{
11 pbr::ViewLightsUniformOffset,
12 prelude::*,
13 render::{
14 camera::ExtractedCamera,
15 extract_component::DynamicUniformIndex,
16 render_asset::RenderAssets,
17 render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
18 render_resource::*,
19 renderer::{RenderContext, RenderDevice, RenderQueue},
20 texture::{FallbackImage, TextureCache},
21 view::ViewUniformOffset,
22 RenderApp, RenderStage,
23 },
24 utils::HashMap,
25};
26use itertools::multizip;
27use serde::Serialize;
28
29pub const ALBEDO_TEXTURE_FORMAT: TextureFormat = TextureFormat::Rgba16Float;
30pub const VARIANCE_TEXTURE_FORMAT: TextureFormat = TextureFormat::R32Float;
31pub const RENDER_TEXTURE_FORMAT: TextureFormat = TextureFormat::Rgba16Float;
32
33pub struct LightPlugin;
34impl Plugin for LightPlugin {
35 fn build(&self, app: &mut App) {
36 if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
37 render_app
38 .init_resource::<ReservoirCache>()
39 .init_resource::<SpecializedComputePipelines<LightPipeline>>()
40 .add_system_to_stage(
41 RenderStage::Prepare,
42 prepare_light_pipeline.after(MeshMaterialSystems::PrepareAssets),
43 )
44 .add_system_to_stage(RenderStage::Prepare, prepare_light_textures)
45 .add_system_to_stage(RenderStage::Queue, queue_light_bind_groups)
46 .add_system_to_stage(RenderStage::Queue, queue_light_pipelines);
47 }
48 }
49}
50
51#[derive(Debug, Default, Clone, Copy, ShaderType)]
52pub struct GpuPackedReservoir {
53 pub radiance: UVec2,
54 pub random: UVec2,
55 pub visible_position: Vec4,
56 pub sample_position: Vec4,
57 pub visible_normal: u32,
58 pub sample_normal: u32,
59 pub reservoir: UVec2,
60}
61
62#[derive(Default, Resource, Clone, ShaderType)]
63pub struct GpuReservoirBuffer {
64 #[size(runtime)]
65 pub data: Vec<GpuPackedReservoir>,
66}
67
68#[derive(Default, Resource, Deref, DerefMut)]
69pub struct ReservoirCache(HashMap<Entity, Vec<StorageBuffer<GpuReservoirBuffer>>>);
70
71#[derive(Resource)]
72pub struct LightPipeline {
73 pub view_layout: BindGroupLayout,
74 pub deferred_layout: BindGroupLayout,
75 pub mesh_material_layout: BindGroupLayout,
76
77 pub texture_count: u32,
78 pub texture_layout: BindGroupLayout,
79
80 pub noise_layout: BindGroupLayout,
81 pub render_layout: BindGroupLayout,
82 pub reservoir_layout: BindGroupLayout,
83}
84
85#[repr(C)]
86#[derive(Default, Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, FromPrimitive)]
87#[serde(rename_all = "snake_case")]
88pub enum LightEntryPoint {
89 #[default]
90 DirectLit = 0,
91 IndirectLitAmbient = 1,
92 SpatialReuse = 2,
93 FullScreenAlbedo = 3,
94}
95
96bitflags::bitflags! {
97 #[repr(transparent)]
98 pub struct LightPipelineKey: u32 {
99 const ENTRY_POINT_BITS = LightPipelineKey::ENTRY_POINT_MASK_BITS;
100 const EMISSIVE_LIT_BIT = 1 << LightPipelineKey::EMISSIVE_LIT_SHIFT_BITS;
101 const RENDER_EMISSIVE_BIT = 1 << LightPipelineKey::RENDER_EMISSIVE_SHIFT_BITS;
102 const MULTIPLE_BOUNCES_BIT = 1 << LightPipelineKey::MULTIPLE_BOUNCES_SHIFT_BITS;
103 const TEXTURE_COUNT_BITS = LightPipelineKey::TEXTURE_COUNT_MASK_BITS << LightPipelineKey::TEXTURE_COUNT_SHIFT_BITS;
104 }
105}
106
107impl LightPipelineKey {
108 const ENTRY_POINT_MASK_BITS: u32 = 0xF;
109 const EMISSIVE_LIT_SHIFT_BITS: u32 = 4;
110 const RENDER_EMISSIVE_SHIFT_BITS: u32 = 5;
111 const MULTIPLE_BOUNCES_SHIFT_BITS: u32 = 6;
112 const TEXTURE_COUNT_MASK_BITS: u32 = 0xFFFF;
113 const TEXTURE_COUNT_SHIFT_BITS: u32 = 32 - 16;
114
115 pub fn from_entry_point(entry_point: LightEntryPoint) -> Self {
116 let entry_point_bits = (entry_point as u32) & Self::ENTRY_POINT_MASK_BITS;
117 Self::from_bits(entry_point_bits).unwrap()
118 }
119
120 pub fn entry_point(&self) -> LightEntryPoint {
121 let entry_point_bits = self.bits & Self::ENTRY_POINT_MASK_BITS;
122 num_traits::FromPrimitive::from_u32(entry_point_bits).unwrap()
123 }
124
125 pub fn from_texture_count(texture_count: u32) -> Self {
126 let texture_count_bits =
127 (texture_count & Self::TEXTURE_COUNT_MASK_BITS) << Self::TEXTURE_COUNT_SHIFT_BITS;
128 Self::from_bits(texture_count_bits).unwrap()
129 }
130
131 pub fn texture_count(&self) -> u32 {
132 (self.bits >> Self::TEXTURE_COUNT_SHIFT_BITS) & Self::TEXTURE_COUNT_MASK_BITS
133 }
134}
135
136impl SpecializedComputePipeline for LightPipeline {
137 type Key = LightPipelineKey;
138
139 fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
140 let mut shader_defs = vec![];
141 if key.texture_count() == 0 {
142 shader_defs.push("NO_TEXTURE".into());
143 }
144 if key.contains(LightPipelineKey::EMISSIVE_LIT_BIT) {
145 shader_defs.push("EMISSIVE_LIT".into());
146 }
147 if key.contains(LightPipelineKey::RENDER_EMISSIVE_BIT) {
148 shader_defs.push("RENDER_EMISSIVE".into());
149 }
150 if key.contains(LightPipelineKey::MULTIPLE_BOUNCES_BIT) {
151 shader_defs.push("MULTIPLE_BOUNCES".into());
152 }
153
154 let entry_point = serde_variant::to_variant_name(&key.entry_point())
155 .unwrap()
156 .into();
157
158 ComputePipelineDescriptor {
159 label: None,
160 layout: Some(vec![
161 self.view_layout.clone(),
162 self.deferred_layout.clone(),
163 self.mesh_material_layout.clone(),
164 self.texture_layout.clone(),
165 self.noise_layout.clone(),
166 self.render_layout.clone(),
167 self.reservoir_layout.clone(),
168 ]),
169 shader: LIGHT_SHADER_HANDLE.typed::<Shader>(),
170 shader_defs,
171 entry_point,
172 }
173 }
174}
175
176fn prepare_light_pipeline(
177 mut commands: Commands,
178 render_device: Res<RenderDevice>,
179 mesh_material_layout: Res<MeshMaterialBindGroupLayout>,
180 texture_layout: Res<TextureBindGroupLayout>,
181 prepass_pipeline: Res<PrepassPipeline>,
182) {
183 if !texture_layout.is_changed() {
184 return;
185 }
186
187 let view_layout = prepass_pipeline.view_layout.clone();
188 let mesh_material_layout = mesh_material_layout.clone();
189
190 let texture_count = texture_layout.texture_count;
191 let texture_layout = texture_layout.layout.clone();
192
193 let deferred_layout = PrepassTextures::bind_group_layout(&render_device);
194 let noise_layout = NoiseTextures::bind_group_layout(&render_device);
195
196 let render_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
197 label: None,
198 entries: &[
199 BindGroupLayoutEntry {
201 binding: 0,
202 visibility: ShaderStages::COMPUTE,
203 ty: BindingType::StorageTexture {
204 access: StorageTextureAccess::ReadWrite,
205 format: ALBEDO_TEXTURE_FORMAT,
206 view_dimension: TextureViewDimension::D2,
207 },
208 count: None,
209 },
210 BindGroupLayoutEntry {
212 binding: 1,
213 visibility: ShaderStages::COMPUTE,
214 ty: BindingType::StorageTexture {
215 access: StorageTextureAccess::ReadWrite,
216 format: VARIANCE_TEXTURE_FORMAT,
217 view_dimension: TextureViewDimension::D2,
218 },
219 count: None,
220 },
221 BindGroupLayoutEntry {
223 binding: 2,
224 visibility: ShaderStages::COMPUTE,
225 ty: BindingType::StorageTexture {
226 access: StorageTextureAccess::ReadWrite,
227 format: RENDER_TEXTURE_FORMAT,
228 view_dimension: TextureViewDimension::D2,
229 },
230 count: None,
231 },
232 ],
233 });
234
235 let reservoir_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
236 label: None,
237 entries: &[
238 BindGroupLayoutEntry {
240 binding: 0,
241 visibility: ShaderStages::COMPUTE,
242 ty: BindingType::Buffer {
243 ty: BufferBindingType::Storage { read_only: true },
244 has_dynamic_offset: false,
245 min_binding_size: Some(GpuReservoirBuffer::min_size()),
246 },
247 count: None,
248 },
249 BindGroupLayoutEntry {
251 binding: 1,
252 visibility: ShaderStages::COMPUTE,
253 ty: BindingType::Buffer {
254 ty: BufferBindingType::Storage { read_only: false },
255 has_dynamic_offset: false,
256 min_binding_size: Some(GpuReservoirBuffer::min_size()),
257 },
258 count: None,
259 },
260 BindGroupLayoutEntry {
262 binding: 2,
263 visibility: ShaderStages::COMPUTE,
264 ty: BindingType::Buffer {
265 ty: BufferBindingType::Storage { read_only: false },
266 has_dynamic_offset: false,
267 min_binding_size: Some(GpuReservoirBuffer::min_size()),
268 },
269 count: None,
270 },
271 BindGroupLayoutEntry {
273 binding: 3,
274 visibility: ShaderStages::COMPUTE,
275 ty: BindingType::Buffer {
276 ty: BufferBindingType::Storage { read_only: false },
277 has_dynamic_offset: false,
278 min_binding_size: Some(GpuReservoirBuffer::min_size()),
279 },
280 count: None,
281 },
282 ],
283 });
284
285 commands.insert_resource(LightPipeline {
286 view_layout,
287 deferred_layout,
288 mesh_material_layout,
289 texture_count,
290 texture_layout,
291 noise_layout,
292 render_layout,
293 reservoir_layout,
294 });
295}
296
297#[derive(Component)]
298pub struct LightTextures {
299 pub head: usize,
301 pub albedo: TextureView,
302 pub variance: [TextureView; 3],
303 pub render: [TextureView; 3],
304}
305
306#[allow(clippy::too_many_arguments)]
307fn prepare_light_textures(
308 mut commands: Commands,
309 render_device: Res<RenderDevice>,
310 render_queue: Res<RenderQueue>,
311 mut texture_cache: ResMut<TextureCache>,
312 mut reservoir_cache: ResMut<ReservoirCache>,
313 cameras: Query<(Entity, &ExtractedCamera, &FrameCounter, &HikariSettings)>,
314) {
315 for (entity, camera, counter, settings) in &cameras {
316 if let Some(size) = camera.physical_target_size {
317 let texture_usage = TextureUsages::TEXTURE_BINDING | TextureUsages::STORAGE_BINDING;
318 let scale = settings.upscale.ratio().recip();
319 let scaled_size = (scale * size.as_vec2()).ceil().as_uvec2();
320 let mut create_texture = |texture_format, size: UVec2| {
321 let extent = Extent3d {
322 width: size.x,
323 height: size.y,
324 depth_or_array_layers: 1,
325 };
326 texture_cache
327 .get(
328 &render_device,
329 TextureDescriptor {
330 label: None,
331 size: extent,
332 mip_level_count: 1,
333 sample_count: 1,
334 dimension: TextureDimension::D2,
335 format: texture_format,
336 usage: texture_usage,
337 },
338 )
339 .default_view
340 };
341
342 if match reservoir_cache.get(&entity) {
343 Some(reservoirs) => {
344 let len = (size.x * size.y) as usize;
345 reservoirs
346 .iter()
347 .any(|buffer| buffer.get().data.len() != len)
348 }
349 None => true,
350 } {
351 let len = (size.x * size.y) as usize;
353 let reservoirs = (0..10)
354 .map(|_| {
355 let mut buffer = StorageBuffer::from(GpuReservoirBuffer {
356 data: vec![GpuPackedReservoir::default(); len],
357 });
358 buffer.write_buffer(&render_device, &render_queue);
359 buffer
360 })
361 .collect();
362 reservoir_cache.insert(entity, reservoirs);
363 }
364
365 macro_rules! create_texture_array {
366 [$texture_format:ident, $size:ident; $count:literal] => {
367 [(); $count].map(|_| create_texture($texture_format, $size))
368 };
369 }
370
371 let variance = create_texture_array![VARIANCE_TEXTURE_FORMAT, scaled_size; 3];
372 let render = create_texture_array![RENDER_TEXTURE_FORMAT, scaled_size; 3];
373 let albedo = create_texture(ALBEDO_TEXTURE_FORMAT, size);
374
375 commands.entity(entity).insert(LightTextures {
376 head: counter.0 % 2,
377 albedo,
378 variance,
379 render,
380 });
381 }
382 }
383}
384
385#[derive(Resource)]
386pub struct CachedLightPipelines {
387 full_screen_albedo: CachedComputePipelineId,
388 direct_lit: CachedComputePipelineId,
389 direct_emissive: CachedComputePipelineId,
390 indirect: CachedComputePipelineId,
391 indirect_multiple_bounces: CachedComputePipelineId,
392 emissive_spatial_reuse: CachedComputePipelineId,
393 indirect_spatial_reuse: CachedComputePipelineId,
394}
395
396fn queue_light_pipelines(
397 mut commands: Commands,
398 pipeline: Res<LightPipeline>,
399 mut pipelines: ResMut<SpecializedComputePipelines<LightPipeline>>,
400 mut pipeline_cache: ResMut<PipelineCache>,
401) {
402 let key = LightPipelineKey::from_texture_count(pipeline.texture_count);
403
404 let full_screen_albedo = {
405 let key = key | LightPipelineKey::from_entry_point(LightEntryPoint::FullScreenAlbedo);
406 pipelines.specialize(&mut pipeline_cache, &pipeline, key)
407 };
408
409 let direct_lit = {
410 let key = key
411 | LightPipelineKey::from_entry_point(LightEntryPoint::DirectLit)
412 | LightPipelineKey::RENDER_EMISSIVE_BIT;
413 pipelines.specialize(&mut pipeline_cache, &pipeline, key)
414 };
415 let direct_emissive = {
416 let key = key
417 | LightPipelineKey::from_entry_point(LightEntryPoint::DirectLit)
418 | LightPipelineKey::EMISSIVE_LIT_BIT;
419 pipelines.specialize(&mut pipeline_cache, &pipeline, key)
420 };
421
422 let indirect = {
423 let key = key | LightPipelineKey::from_entry_point(LightEntryPoint::IndirectLitAmbient);
424 pipelines.specialize(&mut pipeline_cache, &pipeline, key)
425 };
426 let indirect_multiple_bounces = {
427 let key = key
428 | LightPipelineKey::from_entry_point(LightEntryPoint::IndirectLitAmbient)
429 | LightPipelineKey::MULTIPLE_BOUNCES_BIT;
430 pipelines.specialize(&mut pipeline_cache, &pipeline, key)
431 };
432
433 let emissive_spatial_reuse = {
434 let key = key
435 | LightPipelineKey::from_entry_point(LightEntryPoint::SpatialReuse)
436 | LightPipelineKey::EMISSIVE_LIT_BIT;
437 pipelines.specialize(&mut pipeline_cache, &pipeline, key)
438 };
439 let indirect_spatial_reuse = {
440 let key = key | LightPipelineKey::from_entry_point(LightEntryPoint::SpatialReuse);
441 pipelines.specialize(&mut pipeline_cache, &pipeline, key)
442 };
443
444 commands.insert_resource(CachedLightPipelines {
445 full_screen_albedo,
446 direct_lit,
447 direct_emissive,
448 indirect,
449 indirect_multiple_bounces,
450 emissive_spatial_reuse,
451 indirect_spatial_reuse,
452 })
453}
454
455#[derive(Component, Clone)]
456pub struct LightBindGroup {
457 pub noise: BindGroup,
458 pub render: [BindGroup; 3],
459 pub reservoir: [BindGroup; 3],
460}
461
462#[allow(clippy::too_many_arguments)]
463fn queue_light_bind_groups(
464 mut commands: Commands,
465 render_device: Res<RenderDevice>,
466 pipeline: Res<LightPipeline>,
467 noise: Res<NoiseTextures>,
468 images: Res<RenderAssets<Image>>,
469 fallback: Res<FallbackImage>,
470 reservoir_cache: Res<ReservoirCache>,
471 query: Query<(Entity, &LightTextures), With<ExtractedCamera>>,
472) {
473 for (entity, light) in &query {
474 let reservoirs = reservoir_cache.get(&entity).unwrap();
475 if let Some(reservoir_bindings) = reservoirs
476 .iter()
477 .map(|buffer| buffer.binding())
478 .collect::<Option<Vec<_>>>()
479 {
480 let current = light.head;
481 let previous = 1 - current;
482
483 let noise = match noise.as_bind_group(
484 &pipeline.noise_layout,
485 &render_device,
486 &images,
487 &fallback,
488 ) {
489 Ok(noise) => noise,
490 Err(_) => continue,
491 }
492 .bind_group;
493
494 let render = [0, 1, 2].map(|id| {
495 let variance = &light.variance[id];
496 let render = &light.render[id];
497
498 render_device.create_bind_group(&BindGroupDescriptor {
499 label: None,
500 layout: &pipeline.render_layout,
501 entries: &[
502 BindGroupEntry {
503 binding: 0,
504 resource: BindingResource::TextureView(&light.albedo),
505 },
506 BindGroupEntry {
507 binding: 1,
508 resource: BindingResource::TextureView(variance),
509 },
510 BindGroupEntry {
511 binding: 2,
512 resource: BindingResource::TextureView(render),
513 },
514 ],
515 })
516 });
517
518 let reservoir = [(0, 4), (2, 4), (6, 8)].map(|(temporal, spatial)| {
519 let current_temporal = reservoir_bindings[current + temporal].clone();
520 let previous_temporal = reservoir_bindings[previous + temporal].clone();
521 let current_spatial = reservoir_bindings[current + spatial].clone();
522 let previous_spatial = reservoir_bindings[previous + spatial].clone();
523
524 render_device.create_bind_group(&BindGroupDescriptor {
525 label: None,
526 layout: &pipeline.reservoir_layout,
527 entries: &[
528 BindGroupEntry {
529 binding: 0,
530 resource: current_temporal,
531 },
532 BindGroupEntry {
533 binding: 1,
534 resource: previous_temporal,
535 },
536 BindGroupEntry {
537 binding: 2,
538 resource: current_spatial,
539 },
540 BindGroupEntry {
541 binding: 3,
542 resource: previous_spatial,
543 },
544 ],
545 })
546 });
547
548 commands.entity(entity).insert(LightBindGroup {
549 noise,
550 render,
551 reservoir,
552 });
553 }
554 }
555}
556
557#[allow(clippy::type_complexity)]
558pub struct LightNode {
559 query: QueryState<(
560 &'static ExtractedCamera,
561 &'static DynamicUniformIndex<FrameUniform>,
562 &'static ViewUniformOffset,
563 &'static PreviousViewUniformOffset,
564 &'static ViewLightsUniformOffset,
565 &'static DeferredBindGroup,
566 &'static LightBindGroup,
567 &'static HikariSettings,
568 )>,
569}
570
571impl LightNode {
572 pub const IN_VIEW: &'static str = "view";
573
574 pub fn new(world: &mut World) -> Self {
575 Self {
576 query: world.query_filtered(),
577 }
578 }
579}
580
581impl Node for LightNode {
582 fn input(&self) -> Vec<SlotInfo> {
583 vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)]
584 }
585
586 fn update(&mut self, world: &mut World) {
587 self.query.update_archetypes(world);
588 }
589
590 fn run(
591 &self,
592 graph: &mut RenderGraphContext,
593 render_context: &mut RenderContext,
594 world: &World,
595 ) -> Result<(), NodeRunError> {
596 let entity = graph.get_input_entity(Self::IN_VIEW)?;
597 let (
598 camera,
599 frame_uniform,
600 view_uniform,
601 previous_view_uniform,
602 view_lights,
603 deferred_bind_group,
604 light_bind_group,
605 settings,
606 ) = match self.query.get_manual(world, entity) {
607 Ok(query) => query,
608 Err(_) => return Ok(()),
609 };
610 let view_bind_group = match world.get_resource::<PrepassBindGroup>() {
611 Some(bind_group) => &bind_group.view,
612 None => return Ok(()),
613 };
614 let mesh_material_bind_group = match world.get_resource::<MeshMaterialBindGroup>() {
615 Some(bind_group) => bind_group,
616 None => return Ok(()),
617 };
618
619 let pipelines = world.resource::<CachedLightPipelines>();
620 let pipeline_cache = world.resource::<PipelineCache>();
621
622 let size = camera.physical_target_size.unwrap();
623 let scale = settings.upscale.ratio().recip();
624 let scaled_size = (scale * size.as_vec2()).ceil().as_uvec2();
625
626 let mut pass = render_context
627 .command_encoder
628 .begin_compute_pass(&ComputePassDescriptor::default());
629
630 pass.set_bind_group(
631 0,
632 view_bind_group,
633 &[
634 frame_uniform.index(),
635 view_uniform.offset,
636 previous_view_uniform.offset,
637 view_lights.offset,
638 ],
639 );
640 pass.set_bind_group(1, &deferred_bind_group.0, &[]);
641 pass.set_bind_group(2, &mesh_material_bind_group.mesh_material, &[]);
642 pass.set_bind_group(3, &mesh_material_bind_group.texture, &[]);
643 pass.set_bind_group(4, &light_bind_group.noise, &[]);
644
645 if let Some(pipeline) = pipeline_cache.get_compute_pipeline(pipelines.full_screen_albedo) {
647 pass.set_bind_group(5, &light_bind_group.render[0], &[]);
648 pass.set_bind_group(6, &light_bind_group.reservoir[0], &[]);
649 pass.set_pipeline(pipeline);
650
651 let count = (size + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
652 pass.dispatch_workgroups(count.x, count.y, 1);
653 }
654
655 for (render, reservoir, temporal_pipeline, spatial_pipeline, enable_spatial_reuse) in
657 multizip((
658 light_bind_group.render.iter(),
659 light_bind_group.reservoir.iter(),
660 [
661 &pipelines.direct_lit,
662 &pipelines.direct_emissive,
663 match settings.indirect_bounces {
664 x if x < 2 => &pipelines.indirect,
665 _ => &pipelines.indirect_multiple_bounces,
666 },
667 ],
668 [
669 None,
670 Some(&pipelines.emissive_spatial_reuse),
671 Some(&pipelines.indirect_spatial_reuse),
672 ],
673 [
674 false,
675 settings.emissive_spatial_reuse,
676 settings.indirect_spatial_reuse,
677 ],
678 ))
679 {
680 pass.set_bind_group(5, render, &[]);
681 pass.set_bind_group(6, reservoir, &[]);
682
683 if let Some(pipeline) = pipeline_cache.get_compute_pipeline(*temporal_pipeline) {
684 pass.set_pipeline(pipeline);
685
686 let count = (scaled_size + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
687 pass.dispatch_workgroups(count.x, count.y, 1);
688
689 if let Some(pipeline) = spatial_pipeline
690 .filter(|_| enable_spatial_reuse)
691 .and_then(|pipeline| pipeline_cache.get_compute_pipeline(*pipeline))
692 {
693 pass.set_pipeline(pipeline);
694
695 let count = (scaled_size + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
696 pass.dispatch_workgroups(count.x, count.y, 1);
697 }
698 }
699 }
700
701 Ok(())
702 }
703}