tufa/pipeline/render/
mod.rs

1use std::ops::Range;
2
3use consts::VERTEX_BUFFER_LAYOUT;
4use encase::ShaderType;
5use nalgebra::{Vector2, Vector4};
6use wgpu::{
7    BindGroup, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BlendComponent, BlendState,
8    ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState,
9    FragmentState, IndexFormat, MultisampleState, PipelineCompilationOptions,
10    PipelineLayoutDescriptor, PrimitiveState, PrimitiveTopology, RenderPass, ShaderModule,
11    ShaderModuleDescriptor, ShaderStages, StencilState, VertexBufferLayout, VertexState,
12};
13
14use crate::{
15    bindings::{Bindable, BindableResourceId, IndexBuffer, VertexBuffer},
16    gpu::Gpu,
17    misc::ids::PipelineId,
18    DEPTH_TEXTURE_FORMAT, TEXTURE_FORMAT,
19};
20
21use super::PipelineStatus;
22pub mod consts;
23pub mod pass;
24
25#[derive(ShaderType)]
26pub struct Vertex {
27    pub position: Vector4<f32>,
28    pub uv: Vector2<f32>,
29}
30
31impl Vertex {
32    pub const fn new(position: Vector4<f32>, uv: Vector2<f32>) -> Self {
33        Self { position, uv }
34    }
35}
36
37pub struct RenderPipeline {
38    gpu: Gpu,
39
40    id: PipelineId,
41    pipeline: wgpu::RenderPipeline,
42    entries: Vec<BindableResourceId>,
43    bind_group: BindGroup,
44}
45
46#[derive(Clone)]
47pub struct RenderPipelineBuilder {
48    gpu: Gpu,
49
50    module: ShaderModule,
51    vertex_layout: VertexBufferLayout<'static>,
52    instance_layout: Option<VertexBufferLayout<'static>>,
53    bind_group_layout: Vec<BindGroupLayoutEntry>,
54    bind_group: Vec<BindableResourceId>,
55
56    topology: PrimitiveTopology,
57    depth_compare: CompareFunction,
58}
59
60impl RenderPipeline {
61    fn recreate_bind_group(&mut self) {
62        if self.gpu.binding_manager.get_pipeline(self.id).dirty {
63            self.bind_group = self.gpu.binding_manager.create_bind_group(
64                &self.gpu.device,
65                &self.pipeline.get_bind_group_layout(0),
66                &self.entries,
67            );
68        }
69    }
70
71    pub fn draw<T>(
72        &mut self,
73        render_pass: &mut RenderPass,
74        index: &IndexBuffer,
75        vertex: &VertexBuffer<T>,
76        indices: Range<u32>,
77    ) {
78        self.recreate_bind_group();
79
80        render_pass.set_pipeline(&self.pipeline);
81        render_pass.set_bind_group(0, Some(&self.bind_group), &[]);
82        render_pass.set_index_buffer(index.get().slice(..), IndexFormat::Uint32);
83        render_pass.set_vertex_buffer(0, vertex.get().slice(..));
84        render_pass.draw_indexed(indices, 0, 0..1);
85    }
86
87    pub fn draw_quad(&mut self, render_pass: &mut RenderPass, instances: Range<u32>) {
88        self.recreate_bind_group();
89        let (vertex, index) = self.gpu.default_buffers();
90
91        render_pass.set_pipeline(&self.pipeline);
92        render_pass.set_bind_group(0, Some(&self.bind_group), &[]);
93        render_pass.set_index_buffer(index.get().slice(..), IndexFormat::Uint32);
94        render_pass.set_vertex_buffer(0, vertex.get().slice(..));
95        render_pass.draw_indexed(0..6, 0, instances);
96    }
97
98    pub fn instance_quad<T>(
99        &mut self,
100        render_pass: &mut RenderPass,
101        instances: &VertexBuffer<T>,
102        range: Range<u32>,
103    ) {
104        let (vertex, index) = self.gpu.default_buffers();
105
106        render_pass.set_pipeline(&self.pipeline);
107        render_pass.set_bind_group(0, Some(&self.bind_group), &[]);
108        render_pass.set_index_buffer(index.get().slice(..), IndexFormat::Uint32);
109        render_pass.set_vertex_buffer(0, vertex.get().slice(..));
110        render_pass.set_vertex_buffer(1, instances.get().slice(..));
111        render_pass.draw_indexed(0..6, 0, range);
112    }
113}
114
115impl RenderPipelineBuilder {
116    pub fn bind(mut self, entry: &impl Bindable, visibility: ShaderStages) -> Self {
117        let binding = self.bind_group.len() as u32;
118
119        self.bind_group.push(entry.resource_id());
120        self.bind_group_layout.push(BindGroupLayoutEntry {
121            binding,
122            visibility,
123            ty: entry.binding_type(),
124            count: entry.count(),
125        });
126
127        self
128    }
129
130    pub fn vertex_layout(mut self, layout: VertexBufferLayout<'static>) -> Self {
131        self.vertex_layout = layout;
132        self
133    }
134
135    pub fn instance_layout(mut self, layout: VertexBufferLayout<'static>) -> Self {
136        self.instance_layout = Some(layout);
137        self
138    }
139
140    pub fn depth_compare(mut self, compare: CompareFunction) -> Self {
141        self.depth_compare = compare;
142        self
143    }
144
145    pub fn topology(mut self, topology: PrimitiveTopology) -> Self {
146        self.topology = topology;
147        self
148    }
149
150    pub fn finish(self) -> RenderPipeline {
151        let device = &self.gpu.device;
152
153        let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
154            label: None,
155            entries: &self.bind_group_layout,
156        });
157
158        let layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
159            label: None,
160            bind_group_layouts: &[&bind_group_layout],
161            push_constant_ranges: &[],
162        });
163
164        let mut vertex_buffers = vec![self.vertex_layout];
165        if let Some(layout) = self.instance_layout {
166            vertex_buffers.push(layout);
167        }
168
169        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
170            label: None,
171            layout: Some(&layout),
172            vertex: VertexState {
173                module: &self.module,
174                entry_point: Some("vert"),
175                buffers: &vertex_buffers,
176                compilation_options: PipelineCompilationOptions::default(),
177            },
178            fragment: Some(FragmentState {
179                module: &self.module,
180                entry_point: Some("frag"),
181                targets: &[Some(ColorTargetState {
182                    format: TEXTURE_FORMAT,
183                    blend: Some(BlendState {
184                        color: BlendComponent::OVER,
185                        alpha: BlendComponent::OVER,
186                    }),
187                    write_mask: ColorWrites::all(),
188                })],
189                compilation_options: PipelineCompilationOptions::default(),
190            }),
191            primitive: PrimitiveState {
192                topology: self.topology,
193                ..PrimitiveState::default()
194            },
195            depth_stencil: Some(DepthStencilState {
196                format: DEPTH_TEXTURE_FORMAT,
197                depth_write_enabled: true,
198                depth_compare: self.depth_compare,
199                stencil: StencilState::default(),
200                bias: DepthBiasState::default(),
201            }),
202            multisample: MultisampleState::default(),
203            multiview: None,
204            cache: None,
205        });
206
207        let bind_group = self.gpu.binding_manager.create_bind_group(
208            &self.gpu.device,
209            &pipeline.get_bind_group_layout(0),
210            &self.bind_group,
211        );
212
213        let id = PipelineId::new();
214        self.gpu.binding_manager.add_pipeline(
215            id,
216            PipelineStatus {
217                resources: self.bind_group.clone(),
218                dirty: false,
219            },
220        );
221
222        RenderPipeline {
223            gpu: self.gpu,
224            id,
225            pipeline,
226            bind_group,
227            entries: self.bind_group,
228        }
229    }
230}
231
232impl Gpu {
233    pub fn render_pipeline(&self, source: ShaderModuleDescriptor) -> RenderPipelineBuilder {
234        let module = self.device.create_shader_module(source);
235
236        RenderPipelineBuilder {
237            gpu: self.clone(),
238            module,
239            vertex_layout: VERTEX_BUFFER_LAYOUT,
240            instance_layout: None,
241            bind_group_layout: Vec::new(),
242            bind_group: Vec::new(),
243
244            topology: PrimitiveTopology::TriangleList,
245            depth_compare: CompareFunction::LessEqual,
246        }
247    }
248}
249
250impl Drop for RenderPipeline {
251    fn drop(&mut self) {
252        self.gpu.binding_manager.remove_pipeline(self.id);
253    }
254}