1use crate::core_3d::prepare_core_3d_depth_textures;
14use crate::deferred::node::early_deferred_prepass;
15use crate::mip_generation::experimental::depth::{
16 self, early_downsample_depth, late_downsample_depth, DownsampleDepthPipeline,
17 DownsampleDepthPipelines,
18};
19use crate::prepass::node::late_prepass;
20use crate::schedule::{Core3d, Core3dSystems};
21
22use bevy_app::{App, Plugin};
23use bevy_asset::{embedded_asset, load_embedded_asset, AssetId, Assets, Handle};
24use bevy_derive::{Deref, DerefMut};
25use bevy_ecs::{
26 prelude::resource_exists,
27 resource::Resource,
28 schedule::IntoScheduleConfigs as _,
29 system::{Res, ResMut},
30 world::{FromWorld, World},
31};
32use bevy_image::Image;
33use bevy_log::error;
34use bevy_math::{vec2, Vec2};
35use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet};
36use bevy_render::{
37 diagnostic::RecordDiagnostics as _,
38 render_asset::RenderAssets,
39 render_resource::{
40 binding_types::{sampler, texture_2d, texture_storage_2d, uniform_buffer},
41 BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries,
42 CachedComputePipelineId, ComputePassDescriptor, ComputePipelineDescriptor, Extent3d,
43 FilterMode, MipmapFilterMode, PipelineCache, Sampler, SamplerBindingType,
44 SamplerDescriptor, ShaderStages, ShaderType, SpecializedComputePipelines,
45 StorageTextureAccess, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat,
46 TextureFormatFeatureFlags, TextureUsages, TextureView, TextureViewDescriptor,
47 TextureViewDimension, UniformBuffer,
48 },
49 renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue},
50 settings::WgpuFeatures,
51 texture::GpuImage,
52 RenderStartup,
53};
54use bevy_render::{GpuResourceAppExt, Render, RenderApp, RenderSystems};
55use bevy_shader::{Shader, ShaderDefVal};
56use bevy_utils::default;
57
58pub mod experimental;
59
60#[derive(Clone, Resource)]
62pub struct DownsampleShaders {
63 pub depth: Handle<Shader>,
66 pub general: HashMap<TextureFormat, Handle<Shader>>,
71}
72
73const REQUIRED_STORAGE_TEXTURES: u32 = 12;
76
77static TEXTURE_FORMATS: [(TextureFormat, &str); 40] = [
88 (TextureFormat::Rgba8Unorm, "rgba8unorm"),
89 (TextureFormat::Rgba8Snorm, "rgba8snorm"),
90 (TextureFormat::Rgba8Uint, "rgba8uint"),
91 (TextureFormat::Rgba8Sint, "rgba8sint"),
92 (TextureFormat::Rgba16Unorm, "rgba16unorm"),
93 (TextureFormat::Rgba16Snorm, "rgba16snorm"),
94 (TextureFormat::Rgba16Uint, "rgba16uint"),
95 (TextureFormat::Rgba16Sint, "rgba16sint"),
96 (TextureFormat::Rgba16Float, "rgba16float"),
97 (TextureFormat::Rg8Unorm, "rg8unorm"),
98 (TextureFormat::Rg8Snorm, "rg8snorm"),
99 (TextureFormat::Rg8Uint, "rg8uint"),
100 (TextureFormat::Rg8Sint, "rg8sint"),
101 (TextureFormat::Rg16Unorm, "rg16unorm"),
102 (TextureFormat::Rg16Snorm, "rg16snorm"),
103 (TextureFormat::Rg16Uint, "rg16uint"),
104 (TextureFormat::Rg16Sint, "rg16sint"),
105 (TextureFormat::Rg16Float, "rg16float"),
106 (TextureFormat::R32Uint, "r32uint"),
107 (TextureFormat::R32Sint, "r32sint"),
108 (TextureFormat::R32Float, "r32float"),
109 (TextureFormat::Rg32Uint, "rg32uint"),
110 (TextureFormat::Rg32Sint, "rg32sint"),
111 (TextureFormat::Rg32Float, "rg32float"),
112 (TextureFormat::Rgba32Uint, "rgba32uint"),
113 (TextureFormat::Rgba32Sint, "rgba32sint"),
114 (TextureFormat::Rgba32Float, "rgba32float"),
115 (TextureFormat::Bgra8Unorm, "bgra8unorm"),
116 (TextureFormat::R8Unorm, "r8unorm"),
117 (TextureFormat::R8Snorm, "r8snorm"),
118 (TextureFormat::R8Uint, "r8uint"),
119 (TextureFormat::R8Sint, "r8sint"),
120 (TextureFormat::R16Unorm, "r16unorm"),
121 (TextureFormat::R16Snorm, "r16snorm"),
122 (TextureFormat::R16Uint, "r16uint"),
123 (TextureFormat::R16Sint, "r16sint"),
124 (TextureFormat::R16Float, "r16float"),
125 (TextureFormat::Rgb10a2Unorm, "rgb10a2unorm"),
126 (TextureFormat::Rgb10a2Uint, "rgb10a2uint"),
127 (TextureFormat::Rg11b10Ufloat, "rg11b10ufloat"),
128];
129
130#[derive(Resource, Default, Deref, DerefMut)]
144pub struct MipGenerationJobs(pub HashMap<MipGenerationPhaseId, MipGenerationPhase>);
145
146impl MipGenerationJobs {
147 pub fn add(&mut self, phase: MipGenerationPhaseId, image: impl Into<AssetId<Image>>) {
154 self.entry(phase).or_default().push(image.into());
155 }
156}
157
158#[derive(Default, Deref, DerefMut)]
167pub struct MipGenerationPhase(pub Vec<AssetId<Image>>);
168
169#[derive(Clone, Copy, PartialEq, Eq, Hash)]
184pub struct MipGenerationPhaseId(pub u32);
185
186#[derive(Resource, Default)]
192pub struct MipGenerationPipelines {
193 pipelines: HashMap<TextureFormat, MipGenerationTextureFormatPipelines>,
198
199 bind_groups: HashMap<AssetId<Image>, MipGenerationJobBindGroups>,
204}
205
206struct MipGenerationTextureFormatPipelines {
214 downsampling_bind_group_layout_pass_1: BindGroupLayoutDescriptor,
216 downsampling_bind_group_layout_pass_2: BindGroupLayoutDescriptor,
218 downsampling_pipeline_pass_1: CachedComputePipelineId,
220 downsampling_pipeline_pass_2: CachedComputePipelineId,
222}
223
224struct MipGenerationJobBindGroups {
226 downsampling_bind_group_pass_1: BindGroup,
228 downsampling_bind_group_pass_2: BindGroup,
230}
231
232#[derive(Clone, Copy, ShaderType)]
238#[repr(C)]
239pub struct DownsamplingConstants {
240 pub mips: u32,
242 pub inverse_input_size: Vec2,
244 pub _padding: u32,
246}
247
248pub struct MipGenerationPlugin;
253
254impl Plugin for MipGenerationPlugin {
255 fn build(&self, app: &mut App) {
256 embedded_asset!(app, "experimental/downsample_depth.wgsl");
257 embedded_asset!(app, "downsample.wgsl");
258
259 let depth_shader = load_embedded_asset!(app, "experimental/downsample_depth.wgsl");
260
261 let mut shader_assets = app.world_mut().resource_mut::<Assets<Shader>>();
267 let shader_template_source = include_str!("downsample.wgsl");
268 let general_shaders: HashMap<_, _> = TEXTURE_FORMATS
269 .iter()
270 .map(|(target_format, identifier)| {
271 let shader_source =
272 shader_template_source.replace("##TEXTURE_FORMAT##", identifier);
273 (
274 *target_format,
275 shader_assets.add(Shader::from_wgsl(shader_source, "downsample.wgsl")),
276 )
277 })
278 .collect();
279
280 let downsample_shaders = DownsampleShaders {
281 depth: depth_shader,
282 general: general_shaders,
283 };
284 app.insert_resource(downsample_shaders.clone());
285
286 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
287 return;
288 };
289
290 render_app
291 .init_gpu_resource::<SpecializedComputePipelines<DownsampleDepthPipeline>>()
292 .init_resource::<MipGenerationJobs>()
293 .init_resource::<MipGenerationPipelines>()
294 .insert_resource(downsample_shaders)
295 .add_systems(RenderStartup, depth::init_depth_pyramid_dummy_texture)
296 .add_systems(
297 Core3d,
298 (
299 early_downsample_depth
300 .after(early_deferred_prepass)
301 .before(late_prepass),
302 late_downsample_depth.in_set(Core3dSystems::PostProcess),
303 ),
304 )
305 .add_systems(
306 Render,
307 depth::create_downsample_depth_pipelines.in_set(RenderSystems::Prepare),
308 )
309 .add_systems(
310 Render,
311 (
312 depth::prepare_view_depth_pyramids,
313 depth::prepare_downsample_depth_view_bind_groups,
314 )
315 .chain()
316 .in_set(RenderSystems::PrepareResources)
317 .run_if(resource_exists::<DownsampleDepthPipelines>)
318 .after(prepare_core_3d_depth_textures),
319 )
320 .add_systems(
321 Render,
322 prepare_mip_generator_pipelines.in_set(RenderSystems::PrepareResources),
323 )
324 .add_systems(
325 Render,
326 reset_mip_generation_jobs.in_set(RenderSystems::Cleanup),
327 );
328 }
329
330 fn finish(&self, app: &mut App) {
331 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
332 return;
333 };
334
335 render_app.init_gpu_resource::<MipGenerationResources>();
338 }
339}
340
341#[derive(Resource)]
345struct MipGenerationResources {
346 sampler: Sampler,
349}
350
351impl FromWorld for MipGenerationResources {
352 fn from_world(world: &mut World) -> Self {
353 let render_device = world.resource_mut::<RenderDevice>();
354 MipGenerationResources {
355 sampler: render_device.create_sampler(&SamplerDescriptor {
356 label: Some("mip generation sampler"),
357 mag_filter: FilterMode::Linear,
358 min_filter: FilterMode::Linear,
359 mipmap_filter: MipmapFilterMode::Nearest,
360 ..default()
361 }),
362 }
363 }
364}
365
366pub fn generate_mips_for_phase(
378 phase_id: MipGenerationPhaseId,
379 mip_generation_jobs: &MipGenerationJobs,
380 pipeline_cache: &PipelineCache,
381 mip_generation_bind_groups: &MipGenerationPipelines,
382 gpu_images: &RenderAssets<GpuImage>,
383 ctx: &mut RenderContext,
384) {
385 let Some(mip_generation_phase) = mip_generation_jobs.get(&phase_id) else {
386 return;
387 };
388 if mip_generation_phase.is_empty() {
389 return;
391 }
392
393 let diagnostics = ctx.diagnostic_recorder();
394 let diagnostics = diagnostics.as_deref();
395
396 for mip_generation_job in mip_generation_phase.iter() {
397 let Some(gpu_image) = gpu_images.get(*mip_generation_job) else {
398 continue;
399 };
400 let Some(mip_generation_job_bind_groups) = mip_generation_bind_groups
401 .bind_groups
402 .get(mip_generation_job)
403 else {
404 continue;
405 };
406 let Some(mip_generation_pipelines) = mip_generation_bind_groups
407 .pipelines
408 .get(&gpu_image.texture_descriptor.format)
409 else {
410 continue;
411 };
412
413 let (Some(mip_generation_pipeline_pass_1), Some(mip_generation_pipeline_pass_2)) = (
415 pipeline_cache
416 .get_compute_pipeline(mip_generation_pipelines.downsampling_pipeline_pass_1),
417 pipeline_cache
418 .get_compute_pipeline(mip_generation_pipelines.downsampling_pipeline_pass_2),
419 ) else {
420 continue;
421 };
422
423 {
425 let mut compute_pass_1 =
426 ctx.command_encoder()
427 .begin_compute_pass(&ComputePassDescriptor {
428 label: Some("mip generation pass 1"),
429 timestamp_writes: None,
430 });
431 let pass_span = diagnostics.pass_span(&mut compute_pass_1, "mip generation pass 1");
432 compute_pass_1.set_pipeline(mip_generation_pipeline_pass_1);
433 compute_pass_1.set_bind_group(
434 0,
435 &mip_generation_job_bind_groups.downsampling_bind_group_pass_1,
436 &[],
437 );
438 compute_pass_1.dispatch_workgroups(
439 gpu_image.texture_descriptor.size.width.div_ceil(64),
440 gpu_image.texture_descriptor.size.height.div_ceil(64),
441 1,
442 );
443 pass_span.end(&mut compute_pass_1);
444 }
445
446 {
448 let mut compute_pass_2 =
449 ctx.command_encoder()
450 .begin_compute_pass(&ComputePassDescriptor {
451 label: Some("mip generation pass 2"),
452 timestamp_writes: None,
453 });
454 let pass_span = diagnostics.pass_span(&mut compute_pass_2, "mip generation pass 2");
455 compute_pass_2.set_pipeline(mip_generation_pipeline_pass_2);
456 compute_pass_2.set_bind_group(
457 0,
458 &mip_generation_job_bind_groups.downsampling_bind_group_pass_2,
459 &[],
460 );
461 compute_pass_2.dispatch_workgroups(
462 gpu_image.texture_descriptor.size.width.div_ceil(256),
463 gpu_image.texture_descriptor.size.height.div_ceil(256),
464 1,
465 );
466 pass_span.end(&mut compute_pass_2);
467 }
468 }
469}
470
471fn prepare_mip_generator_pipelines(
477 mip_generation_bind_groups: ResMut<MipGenerationPipelines>,
478 mip_generation_resources: Res<MipGenerationResources>,
479 mip_generation_jobs: Res<MipGenerationJobs>,
480 pipeline_cache: Res<PipelineCache>,
481 gpu_images: Res<RenderAssets<GpuImage>>,
482 downsample_shaders: Res<DownsampleShaders>,
483 render_adapter: Res<RenderAdapter>,
484 render_device: Res<RenderDevice>,
485 render_queue: Res<RenderQueue>,
486) {
487 let mip_generation_pipelines = mip_generation_bind_groups.into_inner();
488
489 let combine_downsampling_bind_groups =
492 can_combine_downsampling_bind_groups(&render_adapter, &render_device);
493
494 let mut all_source_images = HashSet::new();
497
498 for mip_generation_phase in mip_generation_jobs.values() {
499 for mip_generation_job in mip_generation_phase.iter() {
500 let Some(gpu_image) = gpu_images.get(*mip_generation_job) else {
501 continue;
502 };
503
504 all_source_images.insert(mip_generation_job);
506
507 let Some(pipelines) = get_or_create_mip_generation_pipelines(
511 &render_device,
512 &pipeline_cache,
513 &downsample_shaders,
514 &mut mip_generation_pipelines.pipelines,
515 gpu_image.texture_descriptor.format,
516 mip_generation_job,
517 combine_downsampling_bind_groups,
518 ) else {
519 continue;
520 };
521
522 let Entry::Vacant(vacant_entry) = mip_generation_pipelines
525 .bind_groups
526 .entry(*mip_generation_job)
527 else {
528 continue;
529 };
530
531 let downsampling_constants_buffer =
532 create_downsampling_constants_buffer(&render_device, &render_queue, gpu_image);
533
534 let (downsampling_bind_group_pass_1, downsampling_bind_group_pass_2) =
535 create_downsampling_bind_groups(
536 &render_device,
537 &pipeline_cache,
538 &mip_generation_resources,
539 &downsampling_constants_buffer,
540 pipelines,
541 gpu_image,
542 combine_downsampling_bind_groups,
543 );
544
545 vacant_entry.insert(MipGenerationJobBindGroups {
546 downsampling_bind_group_pass_1,
547 downsampling_bind_group_pass_2,
548 });
549 }
550 }
551
552 mip_generation_pipelines
557 .bind_groups
558 .retain(|asset_id, _| all_source_images.contains(asset_id));
559}
560
561fn get_or_create_mip_generation_pipelines<'a>(
570 render_device: &RenderDevice,
571 pipeline_cache: &PipelineCache,
572 downsample_shaders: &DownsampleShaders,
573 mip_generation_pipelines: &'a mut HashMap<TextureFormat, MipGenerationTextureFormatPipelines>,
574 target_format: TextureFormat,
575 mip_generation_job: &AssetId<Image>,
576 combine_downsampling_bind_groups: bool,
577) -> Option<&'a MipGenerationTextureFormatPipelines> {
578 match mip_generation_pipelines.entry(target_format) {
579 Entry::Vacant(vacant_entry) => {
580 let Some(downsample_shader) = downsample_shaders.general.get(&target_format) else {
581 error!(
582 "Attempted to generate mips for texture {:?} with format {:?}, but no \
583 downsample shader was available for that texture format",
584 mip_generation_job, target_format
585 );
586 return None;
587 };
588
589 let (downsampling_bind_group_layout_pass_1, downsampling_bind_group_layout_pass_2) =
590 create_downsampling_bind_group_layouts(
591 target_format,
592 combine_downsampling_bind_groups,
593 );
594
595 let (downsampling_pipeline_pass_1, downsampling_pipeline_pass_2) =
596 create_downsampling_pipelines(
597 render_device,
598 pipeline_cache,
599 &downsampling_bind_group_layout_pass_1,
600 &downsampling_bind_group_layout_pass_2,
601 downsample_shader,
602 target_format,
603 combine_downsampling_bind_groups,
604 );
605
606 Some(vacant_entry.insert(MipGenerationTextureFormatPipelines {
607 downsampling_bind_group_layout_pass_1,
608 downsampling_bind_group_layout_pass_2,
609 downsampling_pipeline_pass_1,
610 downsampling_pipeline_pass_2,
611 }))
612 }
613
614 Entry::Occupied(occupied_entry) => Some(occupied_entry.into_mut()),
615 }
616}
617
618fn create_downsampling_bind_group_layouts(
621 target_format: TextureFormat,
622 combine_downsampling_bind_groups: bool,
623) -> (BindGroupLayoutDescriptor, BindGroupLayoutDescriptor) {
624 let texture_sample_type = target_format.sample_type(None, None).expect(
625 "Depth and multisample texture formats shouldn't have mip generation shaders to begin with",
626 );
627 let mips_storage = texture_storage_2d(target_format, StorageTextureAccess::WriteOnly);
628
629 if combine_downsampling_bind_groups {
630 let bind_group_layout_descriptor = BindGroupLayoutDescriptor::new(
631 "combined mip generation bind group layout",
632 &BindGroupLayoutEntries::sequential(
633 ShaderStages::COMPUTE,
634 (
635 sampler(SamplerBindingType::Filtering),
636 uniform_buffer::<DownsamplingConstants>(false),
637 texture_2d(texture_sample_type),
638 mips_storage, mips_storage, mips_storage, mips_storage, mips_storage, texture_storage_2d(target_format, StorageTextureAccess::ReadWrite), mips_storage, mips_storage, mips_storage, mips_storage, mips_storage, mips_storage, ),
651 ),
652 );
653 return (
654 bind_group_layout_descriptor.clone(),
655 bind_group_layout_descriptor,
656 );
657 }
658
659 let bind_group_layout_descriptor_pass_1 = BindGroupLayoutDescriptor::new(
663 "mip generation bind group layout, pass 1",
664 &BindGroupLayoutEntries::sequential(
665 ShaderStages::COMPUTE,
666 (
667 sampler(SamplerBindingType::Filtering),
668 uniform_buffer::<DownsamplingConstants>(false),
669 texture_2d(texture_sample_type),
671 mips_storage, mips_storage, mips_storage, mips_storage, mips_storage, mips_storage, ),
678 ),
679 );
680
681 let bind_group_layout_descriptor_pass_2 = BindGroupLayoutDescriptor::new(
682 "mip generation bind group layout, pass 2",
683 &BindGroupLayoutEntries::sequential(
684 ShaderStages::COMPUTE,
685 (
686 sampler(SamplerBindingType::Filtering),
687 uniform_buffer::<DownsamplingConstants>(false),
688 texture_2d(texture_sample_type),
690 mips_storage, mips_storage, mips_storage, mips_storage, mips_storage, mips_storage, ),
697 ),
698 );
699
700 (
701 bind_group_layout_descriptor_pass_1,
702 bind_group_layout_descriptor_pass_2,
703 )
704}
705
706fn create_downsampling_bind_groups(
713 render_device: &RenderDevice,
714 pipeline_cache: &PipelineCache,
715 mip_generation_resources: &MipGenerationResources,
716 downsampling_constants_buffer: &UniformBuffer<DownsamplingConstants>,
717 pipelines: &MipGenerationTextureFormatPipelines,
718 gpu_image: &GpuImage,
719 combine_downsampling_bind_groups: bool,
720) -> (BindGroup, BindGroup) {
721 let input_texture_view_pass_1 = gpu_image.texture.create_view(&TextureViewDescriptor {
722 label: Some("mip generation input texture view, pass 1"),
723 format: Some(gpu_image.texture.format()),
724 dimension: Some(TextureViewDimension::D2),
725 base_mip_level: 0,
726 mip_level_count: Some(1),
727 ..default()
728 });
729
730 if combine_downsampling_bind_groups {
733 let bind_group = render_device.create_bind_group(
734 Some("combined mip generation bind group"),
735 &pipeline_cache.get_bind_group_layout(&pipelines.downsampling_bind_group_layout_pass_1),
736 &BindGroupEntries::sequential((
737 &mip_generation_resources.sampler,
738 downsampling_constants_buffer,
739 &input_texture_view_pass_1,
740 &get_mip_storage_view(render_device, gpu_image, 1),
741 &get_mip_storage_view(render_device, gpu_image, 2),
742 &get_mip_storage_view(render_device, gpu_image, 3),
743 &get_mip_storage_view(render_device, gpu_image, 4),
744 &get_mip_storage_view(render_device, gpu_image, 5),
745 &get_mip_storage_view(render_device, gpu_image, 6),
746 &get_mip_storage_view(render_device, gpu_image, 7),
747 &get_mip_storage_view(render_device, gpu_image, 8),
748 &get_mip_storage_view(render_device, gpu_image, 9),
749 &get_mip_storage_view(render_device, gpu_image, 10),
750 &get_mip_storage_view(render_device, gpu_image, 11),
751 &get_mip_storage_view(render_device, gpu_image, 12),
752 )),
753 );
754 return (bind_group.clone(), bind_group);
755 }
756
757 let input_texture_view_pass_2 = gpu_image.texture.create_view(&TextureViewDescriptor {
760 label: Some("mip generation input texture view, pass 2"),
761 format: Some(gpu_image.texture.format()),
762 dimension: Some(TextureViewDimension::D2),
763 base_mip_level: gpu_image.texture_descriptor.mip_level_count.min(6),
764 mip_level_count: Some(1),
765 ..default()
766 });
767
768 let bind_group_pass_1 = render_device.create_bind_group(
769 "mip generation bind group, pass 1",
770 &pipeline_cache.get_bind_group_layout(&pipelines.downsampling_bind_group_layout_pass_1),
771 &BindGroupEntries::sequential((
772 &mip_generation_resources.sampler,
773 downsampling_constants_buffer,
774 &input_texture_view_pass_1,
775 &get_mip_storage_view(render_device, gpu_image, 1),
776 &get_mip_storage_view(render_device, gpu_image, 2),
777 &get_mip_storage_view(render_device, gpu_image, 3),
778 &get_mip_storage_view(render_device, gpu_image, 4),
779 &get_mip_storage_view(render_device, gpu_image, 5),
780 &get_mip_storage_view(render_device, gpu_image, 6),
781 )),
782 );
783 let bind_group_pass_2 = render_device.create_bind_group(
784 "mip generation bind group, pass 2",
785 &pipeline_cache.get_bind_group_layout(&pipelines.downsampling_bind_group_layout_pass_2),
786 &BindGroupEntries::sequential((
787 &mip_generation_resources.sampler,
788 downsampling_constants_buffer,
789 &input_texture_view_pass_2,
790 &get_mip_storage_view(render_device, gpu_image, 7),
791 &get_mip_storage_view(render_device, gpu_image, 8),
792 &get_mip_storage_view(render_device, gpu_image, 9),
793 &get_mip_storage_view(render_device, gpu_image, 10),
794 &get_mip_storage_view(render_device, gpu_image, 11),
795 &get_mip_storage_view(render_device, gpu_image, 12),
796 )),
797 );
798
799 (bind_group_pass_1, bind_group_pass_2)
800}
801
802fn create_downsampling_pipelines(
809 render_device: &RenderDevice,
810 pipeline_cache: &PipelineCache,
811 downsampling_bind_group_layout_pass_1: &BindGroupLayoutDescriptor,
812 downsampling_bind_group_layout_pass_2: &BindGroupLayoutDescriptor,
813 downsample_shader: &Handle<Shader>,
814 target_format: TextureFormat,
815 combine_downsampling_bind_groups: bool,
816) -> (CachedComputePipelineId, CachedComputePipelineId) {
817 let mut downsampling_shader_defs = vec![];
818 if render_device.features().contains(WgpuFeatures::SUBGROUP) {
819 downsampling_shader_defs.push(ShaderDefVal::Int("SUBGROUP_SUPPORT".into(), 1));
820 }
821 if combine_downsampling_bind_groups {
822 downsampling_shader_defs.push(ShaderDefVal::Int("COMBINE_BIND_GROUP".into(), 1));
823 }
824
825 let mut downsampling_first_shader_defs = downsampling_shader_defs.clone();
826 let mut downsampling_second_shader_defs = downsampling_shader_defs.clone();
827 if !combine_downsampling_bind_groups {
828 downsampling_first_shader_defs.push(ShaderDefVal::Int("FIRST_PASS".into(), 1));
829 downsampling_second_shader_defs.push(ShaderDefVal::Int("SECOND_PASS".into(), 1));
830 }
831
832 let downsampling_first_pipeline =
835 pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
836 label: Some(format!("mip generation pipeline, pass 1 ({:?})", target_format).into()),
837 layout: vec![downsampling_bind_group_layout_pass_1.clone()],
838 immediate_size: 0,
839 shader: downsample_shader.clone(),
840 shader_defs: downsampling_first_shader_defs,
841 entry_point: Some("downsample_first".into()),
842 zero_initialize_workgroup_memory: false,
843 });
844
845 let downsampling_second_pipeline =
848 pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
849 label: Some(format!("mip generation pipeline, pass 2 ({:?})", target_format).into()),
850 layout: vec![downsampling_bind_group_layout_pass_2.clone()],
851 immediate_size: 0,
852 shader: downsample_shader.clone(),
853 shader_defs: downsampling_second_shader_defs,
854 entry_point: Some("downsample_second".into()),
855 zero_initialize_workgroup_memory: false,
856 });
857
858 (downsampling_first_pipeline, downsampling_second_pipeline)
859}
860
861fn create_downsampling_constants_buffer(
864 render_device: &RenderDevice,
865 render_queue: &RenderQueue,
866 gpu_image: &GpuImage,
867) -> UniformBuffer<DownsamplingConstants> {
868 let downsampling_constants = DownsamplingConstants {
869 mips: gpu_image.texture_descriptor.mip_level_count,
870 inverse_input_size: vec2(
871 1.0 / gpu_image.texture_descriptor.size.width as f32,
872 1.0 / gpu_image.texture_descriptor.size.height as f32,
873 ),
874 _padding: 0,
875 };
876
877 let mut downsampling_constants_buffer = UniformBuffer::from(downsampling_constants);
878 downsampling_constants_buffer.write_buffer(render_device, render_queue);
879 downsampling_constants_buffer
880}
881
882fn get_mip_storage_view(
885 render_device: &RenderDevice,
886 gpu_image: &GpuImage,
887 level: u32,
888) -> TextureView {
889 if level < gpu_image.texture_descriptor.mip_level_count {
892 return gpu_image.texture.create_view(&TextureViewDescriptor {
893 label: Some(&*format!(
894 "mip downsampling storage view {}/{}",
895 level, gpu_image.texture_descriptor.mip_level_count
896 )),
897 format: Some(gpu_image.texture_descriptor.format),
898 dimension: Some(TextureViewDimension::D2),
899 aspect: TextureAspect::All,
900 base_mip_level: level,
901 mip_level_count: Some(1),
902 base_array_layer: 0,
903 array_layer_count: Some(1),
904 usage: Some(TextureUsages::STORAGE_BINDING),
905 });
906 }
907
908 let dummy_texture = render_device.create_texture(&TextureDescriptor {
911 label: Some(&*format!(
912 "mip downsampling dummy storage view {}/{}",
913 level, gpu_image.texture_descriptor.mip_level_count
914 )),
915 size: Extent3d {
916 width: 1,
917 height: 1,
918 depth_or_array_layers: 1,
919 },
920 mip_level_count: 1,
921 sample_count: 1,
922 dimension: TextureDimension::D2,
923 format: gpu_image.texture_descriptor.format,
924 usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
925 view_formats: &[],
926 });
927
928 dummy_texture.create_view(&TextureViewDescriptor::default())
929}
930
931fn reset_mip_generation_jobs(mut mip_generation_jobs: ResMut<MipGenerationJobs>) {
934 mip_generation_jobs.clear();
935}
936
937pub fn can_combine_downsampling_bind_groups(
943 render_adapter: &RenderAdapter,
944 render_device: &RenderDevice,
945) -> bool {
946 let storage_texture_limit = render_device.limits().max_storage_textures_per_shader_stage;
948
949 let read_write_support = render_adapter
951 .get_texture_format_features(TextureFormat::Rgba16Float)
952 .flags
953 .contains(TextureFormatFeatureFlags::STORAGE_READ_WRITE);
954
955 storage_texture_limit >= REQUIRED_STORAGE_TEXTURES && read_write_support
957}