Skip to main content

par_term_render/custom_shader_renderer/
pipeline.rs

1//! Pipeline and bind group creation for custom shader renderer.
2//!
3//! This module contains helpers for creating wgpu bind groups, bind group layouts,
4//! and render pipelines for custom shaders.
5
6use wgpu::*;
7
8use super::cubemap::CubemapTexture;
9use super::textures::ChannelTexture;
10
11/// Create the bind group layout for custom shaders with all 14 entries.
12///
13/// Layout:
14/// - 0: Uniform buffer
15/// - 1: iChannel0 texture (user texture, Shadertoy compatible)
16/// - 2: iChannel0 sampler
17/// - 3: iChannel1 texture (user texture)
18/// - 4: iChannel1 sampler
19/// - 5: iChannel2 texture (user texture)
20/// - 6: iChannel2 sampler
21/// - 7: iChannel3 texture (user texture)
22/// - 8: iChannel3 sampler
23/// - 9: iChannel4 texture (terminal content)
24/// - 10: iChannel4 sampler
25/// - 11: iCubemap texture (cubemap for environment mapping)
26/// - 12: iCubemap sampler
27/// - 13: Custom shader controls uniform buffer
28pub fn create_bind_group_layout(device: &Device) -> BindGroupLayout {
29    device.create_bind_group_layout(&BindGroupLayoutDescriptor {
30        label: Some("Custom Shader Bind Group Layout"),
31        entries: &[
32            // Uniform buffer (binding 0)
33            BindGroupLayoutEntry {
34                binding: 0,
35                visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
36                ty: BindingType::Buffer {
37                    ty: BufferBindingType::Uniform,
38                    has_dynamic_offset: false,
39                    min_binding_size: None,
40                },
41                count: None,
42            },
43            // iChannel0 texture (binding 1) - user texture, Shadertoy compatible
44            BindGroupLayoutEntry {
45                binding: 1,
46                visibility: ShaderStages::FRAGMENT,
47                ty: BindingType::Texture {
48                    sample_type: TextureSampleType::Float { filterable: true },
49                    view_dimension: TextureViewDimension::D2,
50                    multisampled: false,
51                },
52                count: None,
53            },
54            // iChannel0 sampler (binding 2)
55            BindGroupLayoutEntry {
56                binding: 2,
57                visibility: ShaderStages::FRAGMENT,
58                ty: BindingType::Sampler(SamplerBindingType::Filtering),
59                count: None,
60            },
61            // iChannel1 texture (binding 3) - user texture
62            BindGroupLayoutEntry {
63                binding: 3,
64                visibility: ShaderStages::FRAGMENT,
65                ty: BindingType::Texture {
66                    sample_type: TextureSampleType::Float { filterable: true },
67                    view_dimension: TextureViewDimension::D2,
68                    multisampled: false,
69                },
70                count: None,
71            },
72            // iChannel1 sampler (binding 4)
73            BindGroupLayoutEntry {
74                binding: 4,
75                visibility: ShaderStages::FRAGMENT,
76                ty: BindingType::Sampler(SamplerBindingType::Filtering),
77                count: None,
78            },
79            // iChannel2 texture (binding 5) - user texture
80            BindGroupLayoutEntry {
81                binding: 5,
82                visibility: ShaderStages::FRAGMENT,
83                ty: BindingType::Texture {
84                    sample_type: TextureSampleType::Float { filterable: true },
85                    view_dimension: TextureViewDimension::D2,
86                    multisampled: false,
87                },
88                count: None,
89            },
90            // iChannel2 sampler (binding 6)
91            BindGroupLayoutEntry {
92                binding: 6,
93                visibility: ShaderStages::FRAGMENT,
94                ty: BindingType::Sampler(SamplerBindingType::Filtering),
95                count: None,
96            },
97            // iChannel3 texture (binding 7) - user texture
98            BindGroupLayoutEntry {
99                binding: 7,
100                visibility: ShaderStages::FRAGMENT,
101                ty: BindingType::Texture {
102                    sample_type: TextureSampleType::Float { filterable: true },
103                    view_dimension: TextureViewDimension::D2,
104                    multisampled: false,
105                },
106                count: None,
107            },
108            // iChannel3 sampler (binding 8)
109            BindGroupLayoutEntry {
110                binding: 8,
111                visibility: ShaderStages::FRAGMENT,
112                ty: BindingType::Sampler(SamplerBindingType::Filtering),
113                count: None,
114            },
115            // iChannel4 texture (binding 9) - terminal content
116            BindGroupLayoutEntry {
117                binding: 9,
118                visibility: ShaderStages::FRAGMENT,
119                ty: BindingType::Texture {
120                    sample_type: TextureSampleType::Float { filterable: true },
121                    view_dimension: TextureViewDimension::D2,
122                    multisampled: false,
123                },
124                count: None,
125            },
126            // iChannel4 sampler (binding 10)
127            BindGroupLayoutEntry {
128                binding: 10,
129                visibility: ShaderStages::FRAGMENT,
130                ty: BindingType::Sampler(SamplerBindingType::Filtering),
131                count: None,
132            },
133            // iCubemap texture (binding 11) - cubemap for environment mapping
134            BindGroupLayoutEntry {
135                binding: 11,
136                visibility: ShaderStages::FRAGMENT,
137                ty: BindingType::Texture {
138                    sample_type: TextureSampleType::Float { filterable: true },
139                    view_dimension: TextureViewDimension::Cube,
140                    multisampled: false,
141                },
142                count: None,
143            },
144            // iCubemap sampler (binding 12)
145            BindGroupLayoutEntry {
146                binding: 12,
147                visibility: ShaderStages::FRAGMENT,
148                ty: BindingType::Sampler(SamplerBindingType::Filtering),
149                count: None,
150            },
151            // Custom shader controls uniform buffer (binding 13)
152            BindGroupLayoutEntry {
153                binding: 13,
154                visibility: ShaderStages::FRAGMENT,
155                ty: BindingType::Buffer {
156                    ty: BufferBindingType::Uniform,
157                    has_dynamic_offset: false,
158                    min_binding_size: None,
159                },
160                count: None,
161            },
162        ],
163    })
164}
165
166/// Inputs used to create a custom shader bind group.
167pub struct BindGroupInputs<'a> {
168    /// The bind group layout.
169    pub layout: &'a BindGroupLayout,
170    /// Uniform buffer for shader parameters.
171    pub uniform_buffer: &'a Buffer,
172    /// Terminal content texture view (iChannel4).
173    pub intermediate_texture_view: &'a TextureView,
174    /// Uniform buffer for custom shader controls.
175    pub custom_uniform_buffer: &'a Buffer,
176    /// Sampler for the intermediate texture.
177    pub sampler: &'a Sampler,
178    /// Array of 4 channel textures (iChannel0-3, Shadertoy compatible).
179    pub channel_textures: &'a [ChannelTexture; 4],
180    /// Cubemap texture for environment mapping (iCubemap).
181    pub cubemap: &'a CubemapTexture,
182}
183
184/// Create a bind group for the custom shader with all textures and uniforms.
185pub fn create_bind_group(device: &Device, inputs: BindGroupInputs<'_>) -> BindGroup {
186    let BindGroupInputs {
187        layout,
188        uniform_buffer,
189        intermediate_texture_view,
190        custom_uniform_buffer,
191        sampler,
192        channel_textures,
193        cubemap,
194    } = inputs;
195
196    device.create_bind_group(&BindGroupDescriptor {
197        label: Some("Custom Shader Bind Group"),
198        layout,
199        entries: &[
200            BindGroupEntry {
201                binding: 0,
202                resource: uniform_buffer.as_entire_binding(),
203            },
204            // iChannel0 (user texture, Shadertoy compatible)
205            BindGroupEntry {
206                binding: 1,
207                resource: BindingResource::TextureView(&channel_textures[0].view),
208            },
209            BindGroupEntry {
210                binding: 2,
211                resource: BindingResource::Sampler(&channel_textures[0].sampler),
212            },
213            // iChannel1 (user texture)
214            BindGroupEntry {
215                binding: 3,
216                resource: BindingResource::TextureView(&channel_textures[1].view),
217            },
218            BindGroupEntry {
219                binding: 4,
220                resource: BindingResource::Sampler(&channel_textures[1].sampler),
221            },
222            // iChannel2 (user texture)
223            BindGroupEntry {
224                binding: 5,
225                resource: BindingResource::TextureView(&channel_textures[2].view),
226            },
227            BindGroupEntry {
228                binding: 6,
229                resource: BindingResource::Sampler(&channel_textures[2].sampler),
230            },
231            // iChannel3 (user texture)
232            BindGroupEntry {
233                binding: 7,
234                resource: BindingResource::TextureView(&channel_textures[3].view),
235            },
236            BindGroupEntry {
237                binding: 8,
238                resource: BindingResource::Sampler(&channel_textures[3].sampler),
239            },
240            // iChannel4 (terminal content)
241            BindGroupEntry {
242                binding: 9,
243                resource: BindingResource::TextureView(intermediate_texture_view),
244            },
245            BindGroupEntry {
246                binding: 10,
247                resource: BindingResource::Sampler(sampler),
248            },
249            // iCubemap (cubemap for environment mapping)
250            BindGroupEntry {
251                binding: 11,
252                resource: BindingResource::TextureView(&cubemap.view),
253            },
254            BindGroupEntry {
255                binding: 12,
256                resource: BindingResource::Sampler(&cubemap.sampler),
257            },
258            // Custom shader controls
259            BindGroupEntry {
260                binding: 13,
261                resource: custom_uniform_buffer.as_entire_binding(),
262            },
263        ],
264    })
265}
266
267/// Create the render pipeline for custom shaders.
268///
269/// # Arguments
270/// * `device` - The wgpu device
271/// * `shader_module` - Compiled shader module
272/// * `bind_group_layout` - Bind group layout for the pipeline
273/// * `surface_format` - Target surface texture format
274/// * `label` - Optional label for the pipeline
275pub fn create_render_pipeline(
276    device: &Device,
277    shader_module: &ShaderModule,
278    bind_group_layout: &BindGroupLayout,
279    surface_format: TextureFormat,
280    label: Option<&str>,
281) -> RenderPipeline {
282    let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
283        label: Some(label.unwrap_or("Custom Shader Pipeline Layout")),
284        bind_group_layouts: &[Some(bind_group_layout)],
285        immediate_size: 0,
286    });
287
288    device.create_render_pipeline(&RenderPipelineDescriptor {
289        label: Some(label.unwrap_or("Custom Shader Pipeline")),
290        layout: Some(&pipeline_layout),
291        vertex: VertexState {
292            module: shader_module,
293            entry_point: Some("vs_main"),
294            buffers: &[],
295            compilation_options: Default::default(),
296        },
297        fragment: Some(FragmentState {
298            module: shader_module,
299            entry_point: Some("fs_main"),
300            targets: &[Some(ColorTargetState {
301                format: surface_format,
302                // Use premultiplied alpha blending since shader outputs premultiplied colors
303                blend: Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING),
304                write_mask: ColorWrites::ALL,
305            })],
306            compilation_options: Default::default(),
307        }),
308        primitive: PrimitiveState {
309            topology: PrimitiveTopology::TriangleStrip,
310            ..Default::default()
311        },
312        depth_stencil: None,
313        multisample: MultisampleState::default(),
314        cache: None,
315        multiview_mask: None,
316    })
317}