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, 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::{
41 sampler, texture_2d, texture_2d_array, texture_storage_2d, texture_storage_2d_array,
42 uniform_buffer,
43 },
44 BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries,
45 CachedComputePipelineId, ComputePassDescriptor, ComputePipelineDescriptor, Extent3d,
46 FilterMode, MipmapFilterMode, PipelineCache, Sampler, SamplerBindingType,
47 SamplerDescriptor, ShaderStages, ShaderType, SpecializedComputePipeline,
48 SpecializedComputePipelines, StorageTextureAccess, TextureAspect, TextureDescriptor,
49 TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureUsages, TextureView,
50 TextureViewDescriptor, TextureViewDimension, UniformBuffer,
51 },
52 renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue},
53 settings::WgpuFeatures,
54 texture::GpuImage,
55 RenderStartup,
56};
57use bevy_render::{GpuResourceAppExt, Render, RenderApp, RenderSystems};
58use bevy_shader::{Shader, ShaderDefVal};
59use bevy_utils::default;
60
61pub mod experimental;
62
63#[derive(Clone, Resource)]
65pub struct DownsampleShaders {
66 pub depth: Handle<Shader>,
69 pub general: Handle<Shader>,
74}
75
76#[derive(Resource)]
81pub struct DownsamplePipeline {
82 shader: Handle<Shader>,
84 subgroup_support: bool,
86}
87
88impl DownsamplePipeline {
89 pub fn supports_texture_format(format: TextureFormat) -> bool {
91 texture_format_shader_def(format).is_some()
92 }
93}
94
95impl FromWorld for DownsamplePipeline {
96 fn from_world(world: &mut World) -> Self {
97 DownsamplePipeline {
98 shader: world.resource::<DownsampleShaders>().general.clone(),
99 subgroup_support: world
100 .resource::<RenderDevice>()
101 .features()
102 .contains(WgpuFeatures::SUBGROUP),
103 }
104 }
105}
106
107#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
109pub struct DownsamplePipelineKey {
110 pub texture_format: TextureFormat,
115 pub array_texture: bool,
117 pub combine_bind_groups: bool,
121 pub pass: DownsamplePass,
123}
124
125#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
132pub enum DownsamplePass {
133 First,
135 Second,
137}
138
139impl SpecializedComputePipeline for DownsamplePipeline {
140 type Key = DownsamplePipelineKey;
141
142 fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
143 let mut shader_defs = vec![
144 texture_format_shader_def(key.texture_format).unwrap_or_else(|| {
145 panic!(
146 "The downsample shader doesn't support texture format {:?}",
147 key.texture_format
148 )
149 }),
150 ShaderDefVal::Bool("ARRAY_TEXTURE".into(), key.array_texture),
151 ];
152 if self.subgroup_support {
153 shader_defs.push("SUBGROUP_SUPPORT".into());
154 }
155 shader_defs.push(
156 match (key.combine_bind_groups, key.pass) {
157 (true, _) => "COMBINE_BIND_GROUP",
158 (false, DownsamplePass::First) => "SPLIT_BIND_GROUP_FIRST",
159 (false, DownsamplePass::Second) => "SPLIT_BIND_GROUP_SECOND",
160 }
161 .into(),
162 );
163
164 let (pass_number, entry_point) = match key.pass {
165 DownsamplePass::First => (1, "downsample_first"),
166 DownsamplePass::Second => (2, "downsample_second"),
167 };
168
169 ComputePipelineDescriptor {
170 label: Some(
171 format!(
172 "mip generation pipeline, pass {pass_number} ({:?})",
173 key.texture_format
174 )
175 .into(),
176 ),
177 layout: vec![key.bind_group_layout()],
178 shader: self.shader.clone(),
179 shader_defs,
180 entry_point: Some(entry_point.into()),
181 ..default()
182 }
183 }
184}
185
186const REQUIRED_STORAGE_TEXTURES: u32 = 12;
189
190fn texture_format_shader_def(format: TextureFormat) -> Option<ShaderDefVal> {
193 let name = match format {
194 TextureFormat::Rgba8Unorm => "TEXTURE_FORMAT_RGBA8UNORM",
195 TextureFormat::Rgba8Snorm => "TEXTURE_FORMAT_RGBA8SNORM",
196 TextureFormat::Rgba16Unorm => "TEXTURE_FORMAT_RGBA16UNORM",
197 TextureFormat::Rgba16Snorm => "TEXTURE_FORMAT_RGBA16SNORM",
198 TextureFormat::Rgba16Float => "TEXTURE_FORMAT_RGBA16FLOAT",
199 TextureFormat::Rg8Unorm => "TEXTURE_FORMAT_RG8UNORM",
200 TextureFormat::Rg8Snorm => "TEXTURE_FORMAT_RG8SNORM",
201 TextureFormat::Rg16Unorm => "TEXTURE_FORMAT_RG16UNORM",
202 TextureFormat::Rg16Snorm => "TEXTURE_FORMAT_RG16SNORM",
203 TextureFormat::Rg16Float => "TEXTURE_FORMAT_RG16FLOAT",
204 TextureFormat::R32Float => "TEXTURE_FORMAT_R32FLOAT",
205 TextureFormat::Rg32Float => "TEXTURE_FORMAT_RG32FLOAT",
206 TextureFormat::Rgba32Float => "TEXTURE_FORMAT_RGBA32FLOAT",
207 TextureFormat::Bgra8Unorm => "TEXTURE_FORMAT_BGRA8UNORM",
208 TextureFormat::R8Unorm => "TEXTURE_FORMAT_R8UNORM",
209 TextureFormat::R8Snorm => "TEXTURE_FORMAT_R8SNORM",
210 TextureFormat::R16Unorm => "TEXTURE_FORMAT_R16UNORM",
211 TextureFormat::R16Snorm => "TEXTURE_FORMAT_R16SNORM",
212 TextureFormat::R16Float => "TEXTURE_FORMAT_R16FLOAT",
213 TextureFormat::Rgb10a2Unorm => "TEXTURE_FORMAT_RGB10A2UNORM",
214 TextureFormat::Rg11b10Ufloat => "TEXTURE_FORMAT_RG11B10UFLOAT",
215 _ => return None,
216 };
217 Some(name.into())
218}
219
220#[derive(Resource, Default, Deref, DerefMut)]
234pub struct MipGenerationJobs(pub HashMap<MipGenerationPhaseId, MipGenerationPhase>);
235
236impl MipGenerationJobs {
237 pub fn add(&mut self, phase: MipGenerationPhaseId, image: impl Into<AssetId<Image>>) {
244 self.entry(phase).or_default().push(image.into());
245 }
246}
247
248#[derive(Default, Deref, DerefMut)]
257pub struct MipGenerationPhase(pub Vec<AssetId<Image>>);
258
259#[derive(Clone, Copy, PartialEq, Eq, Hash)]
274pub struct MipGenerationPhaseId(pub u32);
275
276#[derive(Resource, Default)]
282pub struct MipGenerationPipelines {
283 jobs: HashMap<AssetId<Image>, MipGenerationJobResources>,
284}
285
286struct MipGenerationJobResources {
289 downsampling_pipeline_pass_1: CachedComputePipelineId,
290 downsampling_pipeline_pass_2: CachedComputePipelineId,
291 downsampling_bind_group_pass_1: BindGroup,
292 downsampling_bind_group_pass_2: BindGroup,
293}
294
295#[derive(Clone, Copy, ShaderType)]
301#[repr(C)]
302pub struct DownsamplingConstants {
303 pub mips: u32,
305 pub inverse_input_size: Vec2,
307 pub _padding: u32,
309}
310
311pub struct MipGenerationPlugin;
316
317impl Plugin for MipGenerationPlugin {
318 fn build(&self, app: &mut App) {
319 embedded_asset!(app, "experimental/downsample_depth.wesl");
320 embedded_asset!(app, "downsample.wesl");
321
322 let depth_shader = load_embedded_asset!(app, "experimental/downsample_depth.wesl");
323
324 let downsample_shaders = DownsampleShaders {
325 depth: depth_shader,
326 general: load_embedded_asset!(app, "downsample.wesl"),
327 };
328 app.insert_resource(downsample_shaders.clone());
329
330 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
331 return;
332 };
333
334 render_app
335 .init_gpu_resource::<SpecializedComputePipelines<DownsampleDepthPipeline>>()
336 .init_gpu_resource::<DownsamplePipeline>()
337 .init_gpu_resource::<SpecializedComputePipelines<DownsamplePipeline>>()
338 .init_resource::<MipGenerationJobs>()
339 .init_gpu_resource::<MipGenerationPipelines>()
340 .init_gpu_resource::<MipGenerationResources>()
341 .insert_resource(downsample_shaders)
342 .add_systems(RenderStartup, depth::init_depth_pyramid_dummy_texture)
343 .add_systems(
344 Core3d,
345 (
346 early_downsample_depth
347 .after(early_deferred_prepass)
348 .before(late_prepass),
349 late_downsample_depth.in_set(Core3dSystems::PostProcess),
350 ),
351 )
352 .add_systems(
353 Render,
354 depth::create_downsample_depth_pipelines.in_set(RenderSystems::Prepare),
355 )
356 .add_systems(
357 Render,
358 (
359 depth::prepare_view_depth_pyramids,
360 depth::prepare_downsample_depth_view_bind_groups,
361 )
362 .chain()
363 .in_set(RenderSystems::PrepareResources)
364 .run_if(resource_exists::<DownsampleDepthPipelines>)
365 .after(prepare_core_3d_depth_textures),
366 )
367 .add_systems(
368 Render,
369 prepare_mip_generator_pipelines.in_set(RenderSystems::PrepareResources),
370 )
371 .add_systems(
372 Render,
373 reset_mip_generation_jobs.in_set(RenderSystems::Cleanup),
374 );
375 }
376}
377
378#[derive(Resource)]
382struct MipGenerationResources {
383 sampler: Sampler,
386}
387
388impl FromWorld for MipGenerationResources {
389 fn from_world(world: &mut World) -> Self {
390 let render_device = world.resource_mut::<RenderDevice>();
391 MipGenerationResources {
392 sampler: render_device.create_sampler(&SamplerDescriptor {
393 label: Some("mip generation sampler"),
394 mag_filter: FilterMode::Linear,
395 min_filter: FilterMode::Linear,
396 mipmap_filter: MipmapFilterMode::Nearest,
397 ..default()
398 }),
399 }
400 }
401}
402
403pub fn generate_mips_for_phase(
415 phase_id: MipGenerationPhaseId,
416 mip_generation_jobs: &MipGenerationJobs,
417 pipeline_cache: &PipelineCache,
418 mip_generation_pipelines: &MipGenerationPipelines,
419 gpu_images: &RenderAssets<GpuImage>,
420 ctx: &mut RenderContext,
421) {
422 let Some(mip_generation_phase) = mip_generation_jobs.get(&phase_id) else {
423 return;
424 };
425 if mip_generation_phase.is_empty() {
426 return;
428 }
429
430 let diagnostics = ctx.diagnostic_recorder();
431 let diagnostics = diagnostics.as_deref();
432
433 for mip_generation_job in mip_generation_phase.iter() {
434 let Some(gpu_image) = gpu_images.get(*mip_generation_job) else {
435 continue;
436 };
437 let Some(job_resources) = mip_generation_pipelines.jobs.get(mip_generation_job) else {
438 continue;
439 };
440
441 let (Some(mip_generation_pipeline_pass_1), Some(mip_generation_pipeline_pass_2)) = (
443 pipeline_cache.get_compute_pipeline(job_resources.downsampling_pipeline_pass_1),
444 pipeline_cache.get_compute_pipeline(job_resources.downsampling_pipeline_pass_2),
445 ) else {
446 continue;
447 };
448
449 {
451 let mut compute_pass_1 =
452 ctx.command_encoder()
453 .begin_compute_pass(&ComputePassDescriptor {
454 label: Some("mip generation pass 1"),
455 timestamp_writes: None,
456 });
457 let pass_span = diagnostics.pass_span(&mut compute_pass_1, "mip generation pass 1");
458 compute_pass_1.set_pipeline(mip_generation_pipeline_pass_1);
459 compute_pass_1.set_bind_group(0, &job_resources.downsampling_bind_group_pass_1, &[]);
460 compute_pass_1.dispatch_workgroups(
461 gpu_image.texture_descriptor.size.width.div_ceil(64),
462 gpu_image.texture_descriptor.size.height.div_ceil(64),
463 1,
464 );
465 pass_span.end(&mut compute_pass_1);
466 }
467
468 {
470 let mut compute_pass_2 =
471 ctx.command_encoder()
472 .begin_compute_pass(&ComputePassDescriptor {
473 label: Some("mip generation pass 2"),
474 timestamp_writes: None,
475 });
476 let pass_span = diagnostics.pass_span(&mut compute_pass_2, "mip generation pass 2");
477 compute_pass_2.set_pipeline(mip_generation_pipeline_pass_2);
478 compute_pass_2.set_bind_group(0, &job_resources.downsampling_bind_group_pass_2, &[]);
479 compute_pass_2.dispatch_workgroups(
480 gpu_image.texture_descriptor.size.width.div_ceil(256),
481 gpu_image.texture_descriptor.size.height.div_ceil(256),
482 1,
483 );
484 pass_span.end(&mut compute_pass_2);
485 }
486 }
487}
488
489fn prepare_mip_generator_pipelines(
495 mip_generation_pipelines: ResMut<MipGenerationPipelines>,
496 mip_generation_resources: Res<MipGenerationResources>,
497 mip_generation_jobs: Res<MipGenerationJobs>,
498 pipeline_cache: Res<PipelineCache>,
499 mut specialized_pipelines: ResMut<SpecializedComputePipelines<DownsamplePipeline>>,
500 downsample_pipeline: Res<DownsamplePipeline>,
501 gpu_images: Res<RenderAssets<GpuImage>>,
502 render_adapter: Res<RenderAdapter>,
503 render_device: Res<RenderDevice>,
504 render_queue: Res<RenderQueue>,
505) {
506 let mip_generation_pipelines = mip_generation_pipelines.into_inner();
507
508 let combine_downsampling_bind_groups =
511 can_combine_downsampling_bind_groups(&render_adapter, &render_device);
512
513 let mut all_source_images = HashSet::new();
516
517 for mip_generation_phase in mip_generation_jobs.values() {
518 for mip_generation_job in mip_generation_phase.iter() {
519 let Some(gpu_image) = gpu_images.get(*mip_generation_job) else {
520 continue;
521 };
522
523 all_source_images.insert(mip_generation_job);
525
526 let Entry::Vacant(vacant_entry) =
528 mip_generation_pipelines.jobs.entry(*mip_generation_job)
529 else {
530 continue;
531 };
532
533 let target_format = gpu_image.texture_descriptor.format;
534 if !DownsamplePipeline::supports_texture_format(target_format) {
535 error!(
536 "Attempted to generate mips for texture {:?} with format {:?}, but no \
537 downsample shader was available for that texture format",
538 mip_generation_job, target_format
539 );
540 continue;
541 }
542
543 let key_pass_1 = DownsamplePipelineKey {
544 texture_format: target_format,
545 array_texture: false,
546 combine_bind_groups: combine_downsampling_bind_groups,
547 pass: DownsamplePass::First,
548 };
549 let key_pass_2 = DownsamplePipelineKey {
550 pass: DownsamplePass::Second,
551 ..key_pass_1
552 };
553
554 let downsampling_constants_buffer =
555 create_downsampling_constants_buffer(&render_device, &render_queue, gpu_image);
556
557 let (downsampling_bind_group_pass_1, downsampling_bind_group_pass_2) =
558 create_downsampling_bind_groups(
559 &render_device,
560 &pipeline_cache,
561 &mip_generation_resources,
562 &downsampling_constants_buffer,
563 gpu_image,
564 key_pass_1,
565 key_pass_2,
566 );
567
568 vacant_entry.insert(MipGenerationJobResources {
569 downsampling_pipeline_pass_1: specialized_pipelines.specialize(
570 &pipeline_cache,
571 &downsample_pipeline,
572 key_pass_1,
573 ),
574 downsampling_pipeline_pass_2: specialized_pipelines.specialize(
575 &pipeline_cache,
576 &downsample_pipeline,
577 key_pass_2,
578 ),
579 downsampling_bind_group_pass_1,
580 downsampling_bind_group_pass_2,
581 });
582 }
583 }
584
585 mip_generation_pipelines
590 .jobs
591 .retain(|asset_id, _| all_source_images.contains(asset_id));
592}
593
594impl DownsamplePipelineKey {
595 pub fn bind_group_layout(&self) -> BindGroupLayoutDescriptor {
598 let texture_sample_type = self.texture_format.sample_type(None, None).expect(
599 "Depth and multisample texture formats shouldn't have mip generation shaders to begin with",
600 );
601 let source_texture = if self.array_texture {
602 texture_2d_array(texture_sample_type)
603 } else {
604 texture_2d(texture_sample_type)
605 };
606 let storage = |access| {
607 if self.array_texture {
608 texture_storage_2d_array(self.texture_format, access)
609 } else {
610 texture_storage_2d(self.texture_format, access)
611 }
612 };
613 let mips_storage = storage(StorageTextureAccess::WriteOnly);
614
615 if self.combine_bind_groups {
616 let mip6_storage = storage(StorageTextureAccess::ReadWrite);
617 return BindGroupLayoutDescriptor::new(
618 "combined mip generation bind group layout",
619 &BindGroupLayoutEntries::sequential(
620 ShaderStages::COMPUTE,
621 (
622 sampler(SamplerBindingType::Filtering),
623 uniform_buffer::<DownsamplingConstants>(false),
624 source_texture,
625 mips_storage, mips_storage, mips_storage, mips_storage, mips_storage, mip6_storage, mips_storage, mips_storage, mips_storage, mips_storage, mips_storage, mips_storage, ),
638 ),
639 );
640 }
641
642 let label = match self.pass {
643 DownsamplePass::First => "mip generation bind group layout, pass 1",
644 DownsamplePass::Second => "mip generation bind group layout, pass 2",
645 };
646 BindGroupLayoutDescriptor::new(
647 label,
648 &BindGroupLayoutEntries::sequential(
649 ShaderStages::COMPUTE,
650 (
651 sampler(SamplerBindingType::Filtering),
652 uniform_buffer::<DownsamplingConstants>(false),
653 source_texture, mips_storage, mips_storage, mips_storage, mips_storage, mips_storage, mips_storage, ),
661 ),
662 )
663 }
664}
665
666fn create_downsampling_bind_groups(
673 render_device: &RenderDevice,
674 pipeline_cache: &PipelineCache,
675 mip_generation_resources: &MipGenerationResources,
676 downsampling_constants_buffer: &UniformBuffer<DownsamplingConstants>,
677 gpu_image: &GpuImage,
678 key_pass_1: DownsamplePipelineKey,
679 key_pass_2: DownsamplePipelineKey,
680) -> (BindGroup, BindGroup) {
681 let input_texture_view_pass_1 = gpu_image.texture.create_view(&TextureViewDescriptor {
682 label: Some("mip generation input texture view, pass 1"),
683 format: Some(gpu_image.texture.format()),
684 dimension: Some(TextureViewDimension::D2),
685 base_mip_level: 0,
686 mip_level_count: Some(1),
687 ..default()
688 });
689
690 if key_pass_1.combine_bind_groups {
693 let bind_group = render_device.create_bind_group(
694 Some("combined mip generation bind group"),
695 &pipeline_cache.get_bind_group_layout(&key_pass_1.bind_group_layout()),
696 &BindGroupEntries::sequential((
697 &mip_generation_resources.sampler,
698 downsampling_constants_buffer,
699 &input_texture_view_pass_1,
700 &get_mip_storage_view(render_device, gpu_image, 1),
701 &get_mip_storage_view(render_device, gpu_image, 2),
702 &get_mip_storage_view(render_device, gpu_image, 3),
703 &get_mip_storage_view(render_device, gpu_image, 4),
704 &get_mip_storage_view(render_device, gpu_image, 5),
705 &get_mip_storage_view(render_device, gpu_image, 6),
706 &get_mip_storage_view(render_device, gpu_image, 7),
707 &get_mip_storage_view(render_device, gpu_image, 8),
708 &get_mip_storage_view(render_device, gpu_image, 9),
709 &get_mip_storage_view(render_device, gpu_image, 10),
710 &get_mip_storage_view(render_device, gpu_image, 11),
711 &get_mip_storage_view(render_device, gpu_image, 12),
712 )),
713 );
714 return (bind_group.clone(), bind_group);
715 }
716
717 let input_texture_view_pass_2 = gpu_image.texture.create_view(&TextureViewDescriptor {
720 label: Some("mip generation input texture view, pass 2"),
721 format: Some(gpu_image.texture.format()),
722 dimension: Some(TextureViewDimension::D2),
723 base_mip_level: gpu_image.texture_descriptor.mip_level_count.min(6),
724 mip_level_count: Some(1),
725 ..default()
726 });
727
728 let bind_group_pass_1 = render_device.create_bind_group(
729 "mip generation bind group, pass 1",
730 &pipeline_cache.get_bind_group_layout(&key_pass_1.bind_group_layout()),
731 &BindGroupEntries::sequential((
732 &mip_generation_resources.sampler,
733 downsampling_constants_buffer,
734 &input_texture_view_pass_1,
735 &get_mip_storage_view(render_device, gpu_image, 1),
736 &get_mip_storage_view(render_device, gpu_image, 2),
737 &get_mip_storage_view(render_device, gpu_image, 3),
738 &get_mip_storage_view(render_device, gpu_image, 4),
739 &get_mip_storage_view(render_device, gpu_image, 5),
740 &get_mip_storage_view(render_device, gpu_image, 6),
741 )),
742 );
743 let bind_group_pass_2 = render_device.create_bind_group(
744 "mip generation bind group, pass 2",
745 &pipeline_cache.get_bind_group_layout(&key_pass_2.bind_group_layout()),
746 &BindGroupEntries::sequential((
747 &mip_generation_resources.sampler,
748 downsampling_constants_buffer,
749 &input_texture_view_pass_2,
750 &get_mip_storage_view(render_device, gpu_image, 7),
751 &get_mip_storage_view(render_device, gpu_image, 8),
752 &get_mip_storage_view(render_device, gpu_image, 9),
753 &get_mip_storage_view(render_device, gpu_image, 10),
754 &get_mip_storage_view(render_device, gpu_image, 11),
755 &get_mip_storage_view(render_device, gpu_image, 12),
756 )),
757 );
758
759 (bind_group_pass_1, bind_group_pass_2)
760}
761
762fn create_downsampling_constants_buffer(
765 render_device: &RenderDevice,
766 render_queue: &RenderQueue,
767 gpu_image: &GpuImage,
768) -> UniformBuffer<DownsamplingConstants> {
769 let downsampling_constants = DownsamplingConstants {
770 mips: gpu_image.texture_descriptor.mip_level_count,
771 inverse_input_size: vec2(
772 1.0 / gpu_image.texture_descriptor.size.width as f32,
773 1.0 / gpu_image.texture_descriptor.size.height as f32,
774 ),
775 _padding: 0,
776 };
777
778 let mut downsampling_constants_buffer = UniformBuffer::from(downsampling_constants);
779 downsampling_constants_buffer.write_buffer(render_device, render_queue);
780 downsampling_constants_buffer
781}
782
783fn get_mip_storage_view(
786 render_device: &RenderDevice,
787 gpu_image: &GpuImage,
788 level: u32,
789) -> TextureView {
790 if level < gpu_image.texture_descriptor.mip_level_count {
793 return gpu_image.texture.create_view(&TextureViewDescriptor {
794 label: Some(&*format!(
795 "mip downsampling storage view {}/{}",
796 level, gpu_image.texture_descriptor.mip_level_count
797 )),
798 format: Some(gpu_image.texture_descriptor.format),
799 dimension: Some(TextureViewDimension::D2),
800 aspect: TextureAspect::All,
801 base_mip_level: level,
802 mip_level_count: Some(1),
803 base_array_layer: 0,
804 array_layer_count: Some(1),
805 usage: Some(TextureUsages::STORAGE_BINDING),
806 });
807 }
808
809 let dummy_texture = render_device.create_texture(&TextureDescriptor {
812 label: Some(&*format!(
813 "mip downsampling dummy storage view {}/{}",
814 level, gpu_image.texture_descriptor.mip_level_count
815 )),
816 size: Extent3d {
817 width: 1,
818 height: 1,
819 depth_or_array_layers: 1,
820 },
821 mip_level_count: 1,
822 sample_count: 1,
823 dimension: TextureDimension::D2,
824 format: gpu_image.texture_descriptor.format,
825 usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
826 view_formats: &[],
827 });
828
829 dummy_texture.create_view(&TextureViewDescriptor::default())
830}
831
832fn reset_mip_generation_jobs(mut mip_generation_jobs: ResMut<MipGenerationJobs>) {
835 mip_generation_jobs.clear();
836}
837
838pub fn can_combine_downsampling_bind_groups(
844 render_adapter: &RenderAdapter,
845 render_device: &RenderDevice,
846) -> bool {
847 let storage_texture_limit = render_device.limits().max_storage_textures_per_shader_stage;
849
850 let read_write_support = render_adapter
852 .get_texture_format_features(TextureFormat::Rgba16Float)
853 .flags
854 .contains(TextureFormatFeatureFlags::STORAGE_READ_WRITE);
855
856 storage_texture_limit >= REQUIRED_STORAGE_TEXTURES && read_write_support
858}