comfy_wgpu/
utils.rs

1use crate::*;
2
3pub const FRAG_SHADER_PREFIX: &str = include_str!(concat!(
4    env!("CARGO_MANIFEST_DIR"),
5    "/shaders/frag-shader-prefix.wgsl"
6));
7
8pub const CAMERA_BIND_GROUP_PREFIX: &str = include_str!(concat!(
9    env!("CARGO_MANIFEST_DIR"),
10    "/shaders/camera-bind-group.wgsl"
11));
12
13pub const SHADER_POST_PROCESSING_VERTEX: &str = include_str!(concat!(
14    env!("CARGO_MANIFEST_DIR"),
15    "/shaders/post_processing_vertex.wgsl"
16));
17
18pub const COPY_SHADER_SRC: &str =
19    include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/copy.wgsl"));
20
21#[macro_export]
22macro_rules! engine_shader_source {
23    ($name:literal) => {{
24        let shader = include_str!(concat!(
25            env!("CARGO_MANIFEST_DIR"),
26            "/shaders/",
27            $name,
28            ".wgsl"
29        ));
30
31        shader
32    }};
33}
34
35pub fn sprite_shader_from_fragment(source: &str) -> String {
36    format!("{}{}{}", CAMERA_BIND_GROUP_PREFIX, FRAG_SHADER_PREFIX, source)
37}
38
39pub fn post_process_shader_from_fragment(source: &str) -> String {
40    format!(
41        "{}{}{}",
42        CAMERA_BIND_GROUP_PREFIX, SHADER_POST_PROCESSING_VERTEX, source
43    )
44}
45
46#[macro_export]
47macro_rules! create_engine_post_processing_shader {
48    ($shaders:expr, $name:literal) => {{
49        let full_shader =
50            post_process_shader_from_fragment(engine_shader_source!($name));
51
52        let shader_id =
53            create_shader($shaders, $name, &full_shader, HashMap::new())
54                .unwrap();
55
56        $shaders.get(shader_id).unwrap().clone()
57    }};
58}
59
60pub fn load_texture_from_engine_bytes(
61    context: &GraphicsContext,
62    name: &str,
63    bytes: &[u8],
64    textures: &mut TextureMap,
65    address_mode: wgpu::AddressMode,
66) {
67    let img = image::load_from_memory(bytes).expect("must be valid image");
68    let texture = Texture::from_image_ex(
69        &context.device,
70        &context.queue,
71        &img,
72        Some(name),
73        false,
74        address_mode,
75    )
76    .unwrap();
77
78    load_texture_with_image(context, name, img, texture, textures);
79}
80
81/// Loads a pre-created `Texture` with an associated `DynamicImage`
82/// into the asset store.
83///
84/// Useful for when the user wants to create a Texture on their own,
85/// e.g. by using a more exotic format and way of loading of the image.
86pub fn load_texture_with_image(
87    context: &GraphicsContext,
88    name: &str,
89    img: DynamicImage,
90    texture: Texture,
91    textures: &mut TextureMap,
92) {
93    let handle = texture_path(name);
94
95    let bind_group = context.device.simple_bind_group(
96        Some(&format!("{}_bind_group", name)),
97        &texture,
98        &context.texture_layout,
99    );
100
101    ASSETS.borrow_mut().insert_handle(name, handle);
102    ASSETS
103        .borrow_mut()
104        .texture_image_map
105        .lock()
106        .insert(handle, Arc::new(img.to_rgba8()));
107    textures.insert(handle, BindableTexture { bind_group, texture });
108}
109
110pub struct MipmapGenerator {
111    pub format: wgpu::TextureFormat,
112    pub blit_pipeline: wgpu::RenderPipeline,
113    pub blit_layout: wgpu::BindGroupLayout,
114}
115
116impl MipmapGenerator {
117    pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
118        let blit_pipeline = {
119            // TODO: unify with other shaders
120            let shader =
121                device.create_shader_module(wgpu::ShaderModuleDescriptor {
122                    label: Some("Blit Shader"),
123                    source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(
124                        engine_shader_source!("blit"),
125                    )),
126                });
127
128            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
129                label: Some("Blit Render Pipeline"),
130                layout: None,
131                vertex: wgpu::VertexState {
132                    module: &shader,
133                    entry_point: "vs_main",
134                    buffers: &[],
135                },
136                fragment: Some(wgpu::FragmentState {
137                    module: &shader,
138                    entry_point: "fs_main",
139                    targets: &[Some(format.into())],
140                }),
141                primitive: wgpu::PrimitiveState {
142                    topology: wgpu::PrimitiveTopology::TriangleList,
143                    ..Default::default()
144                },
145                depth_stencil: None,
146                multisample: wgpu::MultisampleState::default(),
147                multiview: None,
148            })
149        };
150
151        let blit_layout = blit_pipeline.get_bind_group_layout(0);
152
153        Self { format, blit_pipeline, blit_layout }
154    }
155
156    pub fn generate_mipmaps(
157        &self,
158        encoder: &mut wgpu::CommandEncoder,
159        device: &wgpu::Device,
160        texture: &wgpu::Texture,
161        mip_count: u32,
162    ) {
163        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
164            label: Some("Mip Sampler"),
165            address_mode_u: wgpu::AddressMode::ClampToEdge,
166            address_mode_v: wgpu::AddressMode::ClampToEdge,
167            address_mode_w: wgpu::AddressMode::ClampToEdge,
168            mag_filter: wgpu::FilterMode::Linear,
169            min_filter: wgpu::FilterMode::Linear,
170            mipmap_filter: wgpu::FilterMode::Nearest,
171            ..Default::default()
172        });
173
174        let views = (0..mip_count)
175            .map(|mip| {
176                texture.create_view(&wgpu::TextureViewDescriptor {
177                    label: Some("Mip View"),
178                    format: None,
179                    dimension: None,
180                    aspect: wgpu::TextureAspect::All,
181                    base_mip_level: mip,
182                    mip_level_count: Some(1),
183                    base_array_layer: 0,
184                    array_layer_count: None,
185                })
186            })
187            .collect::<Vec<_>>();
188
189        for target_mip in 1..mip_count as usize {
190            let bind_group =
191                device.create_bind_group(&wgpu::BindGroupDescriptor {
192                    layout: &self.blit_layout,
193                    entries: &[
194                        wgpu::BindGroupEntry {
195                            binding: 0,
196                            resource: wgpu::BindingResource::TextureView(
197                                &views[target_mip - 1],
198                            ),
199                        },
200                        wgpu::BindGroupEntry {
201                            binding: 1,
202                            resource: wgpu::BindingResource::Sampler(&sampler),
203                        },
204                    ],
205                    label: None,
206                });
207
208            let mut rpass =
209                encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
210                    label: None,
211                    color_attachments: &[Some(
212                        wgpu::RenderPassColorAttachment {
213                            view: &views[target_mip],
214                            resolve_target: None,
215                            ops: wgpu::Operations {
216                                load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
217                                store: wgpu::StoreOp::Store,
218                            },
219                        },
220                    )],
221                    depth_stencil_attachment: None,
222                    timestamp_writes: None,
223                    occlusion_query_set: None,
224                });
225
226            rpass.set_pipeline(&self.blit_pipeline);
227            rpass.set_bind_group(0, &bind_group, &[]);
228            rpass.draw(0..3, 0..1);
229        }
230    }
231}