blade_render/render/
env_map.rs1use std::num::NonZeroU32;
2
3use crate::DummyResources;
4
5#[repr(C)]
6#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
7struct EnvPreprocParams {
8 target_level: u32,
9}
10
11#[derive(blade_macros::ShaderData)]
12struct EnvPreprocData {
13 source: blade_graphics::TextureView,
14 destination: blade_graphics::TextureView,
15 params: EnvPreprocParams,
16}
17
18pub struct EnvironmentMap {
19 pub main_view: blade_graphics::TextureView,
20 pub size: blade_graphics::Extent,
21 pub weight_texture: blade_graphics::Texture,
22 pub weight_view: blade_graphics::TextureView,
23 pub weight_mips: Vec<blade_graphics::TextureView>,
24 pub prepare_pipeline: blade_graphics::ComputePipeline,
25}
26
27impl EnvironmentMap {
28 pub fn init_pipeline(
29 shader: &blade_graphics::Shader,
30 gpu: &blade_graphics::Context,
31 ) -> Result<blade_graphics::ComputePipeline, &'static str> {
32 let layout = <EnvPreprocData as blade_graphics::ShaderData>::layout();
33 shader.check_struct_size::<EnvPreprocParams>();
34
35 Ok(
36 gpu.create_compute_pipeline(blade_graphics::ComputePipelineDesc {
37 name: "env-prepare",
38 data_layouts: &[&layout],
39 compute: shader.at("downsample"),
40 }),
41 )
42 }
43
44 pub fn with_pipeline(
45 dummy: &DummyResources,
46 prepare_pipeline: blade_graphics::ComputePipeline,
47 ) -> Self {
48 Self {
49 main_view: dummy.white_view,
50 size: blade_graphics::Extent::default(),
51 weight_texture: blade_graphics::Texture::default(),
52 weight_view: dummy.red_view,
53 weight_mips: Vec::new(),
54 prepare_pipeline,
55 }
56 }
57
58 pub fn new(
59 shader: &blade_graphics::Shader,
60 dummy: &DummyResources,
61 gpu: &blade_graphics::Context,
62 ) -> Self {
63 Self::with_pipeline(dummy, Self::init_pipeline(shader, gpu).unwrap())
64 }
65
66 fn weight_size(&self) -> blade_graphics::Extent {
67 blade_graphics::Extent {
69 width: self.size.width.next_power_of_two() / 2,
70 height: self.size.height.next_power_of_two() / 2,
71 depth: 1,
72 }
73 }
74
75 pub fn destroy(&mut self, gpu: &blade_graphics::Context) {
76 if self.weight_texture != blade_graphics::Texture::default() {
77 gpu.destroy_texture(self.weight_texture);
78 gpu.destroy_texture_view(self.weight_view);
79 }
80 for view in self.weight_mips.drain(..) {
81 gpu.destroy_texture_view(view);
82 }
83 gpu.destroy_compute_pipeline(&mut self.prepare_pipeline);
84 }
85
86 pub fn assign(
87 &mut self,
88 view: blade_graphics::TextureView,
89 extent: blade_graphics::Extent,
90 encoder: &mut blade_graphics::CommandEncoder,
91 gpu: &blade_graphics::Context,
92 ) {
93 if self.main_view == view {
94 return;
95 }
96 self.main_view = view;
97 self.size = extent;
98 self.destroy(gpu);
99
100 let mip_level_count = extent
101 .width
102 .max(extent.height)
103 .next_power_of_two()
104 .trailing_zeros();
105 let weight_extent = self.weight_size();
106 let format = blade_graphics::TextureFormat::Rgba16Float;
107 self.weight_texture = gpu.create_texture(blade_graphics::TextureDesc {
108 name: "env-weight",
109 format,
110 size: weight_extent,
111 dimension: blade_graphics::TextureDimension::D2,
112 array_layer_count: 1,
113 mip_level_count,
114 usage: blade_graphics::TextureUsage::RESOURCE | blade_graphics::TextureUsage::STORAGE,
115 sample_count: 1,
116 });
117 self.weight_view = gpu.create_texture_view(
118 self.weight_texture,
119 blade_graphics::TextureViewDesc {
120 name: "env-weight",
121 format,
122 dimension: blade_graphics::ViewDimension::D2,
123 subresources: &Default::default(),
124 },
125 );
126 for base_mip_level in 0..mip_level_count {
127 let view = gpu.create_texture_view(
128 self.weight_texture,
129 blade_graphics::TextureViewDesc {
130 name: &format!("env-weight-mip{}", base_mip_level),
131 format,
132 dimension: blade_graphics::ViewDimension::D2,
133 subresources: &blade_graphics::TextureSubresources {
134 base_mip_level,
135 mip_level_count: NonZeroU32::new(1),
136 ..Default::default()
137 },
138 },
139 );
140 self.weight_mips.push(view);
141 }
142
143 encoder.init_texture(self.weight_texture);
144 for target_level in 0..mip_level_count {
145 let groups = self
146 .prepare_pipeline
147 .get_dispatch_for(weight_extent.at_mip_level(target_level));
148 let mut compute = encoder.compute("pre-process env map");
149 let mut pass = compute.with(&self.prepare_pipeline);
150 pass.bind(
151 0,
152 &EnvPreprocData {
153 source: if target_level == 0 {
154 view
155 } else {
156 self.weight_mips[target_level as usize - 1]
157 },
158 destination: self.weight_mips[target_level as usize],
159 params: EnvPreprocParams { target_level },
160 },
161 );
162 pass.dispatch(groups);
163 }
164 }
165}