Skip to main content

wisp/render/
quad_pipeline.rs

1//! Textured-quad pipeline.
2//!
3//! M0.6 introduces this. M0.9 evolves it into the sprite batcher (instancing
4//! and shared uniform buffer per batch).
5
6use bytemuck::{Pod, Zeroable};
7use glam::Mat4;
8use wgpu::util::DeviceExt;
9
10use crate::application::Application;
11use crate::color::Color;
12use crate::texture::Texture;
13
14#[repr(C)]
15#[derive(Clone, Copy, Pod, Zeroable)]
16struct QuadUniforms {
17    model: [[f32; 4]; 4],
18    tint: [f32; 4],
19}
20
21pub(crate) struct QuadPipeline {
22    pipeline: wgpu::RenderPipeline,
23    uniforms_layout: wgpu::BindGroupLayout,
24    texture_layout: wgpu::BindGroupLayout,
25}
26
27impl QuadPipeline {
28    pub(crate) fn new(app: &Application, output_format: wgpu::TextureFormat) -> Self {
29        let device = app.device();
30        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
31            label: Some("wisp::quad"),
32            source: wgpu::ShaderSource::Wgsl(include_str!("../../shaders/quad.wgsl").into()),
33        });
34
35        let uniforms_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
36            label: Some("wisp::quad uniforms layout"),
37            entries: &[wgpu::BindGroupLayoutEntry {
38                binding: 0,
39                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
40                ty: wgpu::BindingType::Buffer {
41                    ty: wgpu::BufferBindingType::Uniform,
42                    has_dynamic_offset: false,
43                    min_binding_size: None,
44                },
45                count: None,
46            }],
47        });
48
49        let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
50            label: Some("wisp::quad texture layout"),
51            entries: &[
52                wgpu::BindGroupLayoutEntry {
53                    binding: 0,
54                    visibility: wgpu::ShaderStages::FRAGMENT,
55                    ty: wgpu::BindingType::Texture {
56                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
57                        view_dimension: wgpu::TextureViewDimension::D2,
58                        multisampled: false,
59                    },
60                    count: None,
61                },
62                wgpu::BindGroupLayoutEntry {
63                    binding: 1,
64                    visibility: wgpu::ShaderStages::FRAGMENT,
65                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
66                    count: None,
67                },
68            ],
69        });
70
71        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
72            label: Some("wisp::quad pipeline layout"),
73            bind_group_layouts: &[&uniforms_layout, &texture_layout],
74            push_constant_ranges: &[],
75        });
76
77        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
78            label: Some("wisp::quad pipeline"),
79            layout: Some(&pipeline_layout),
80            vertex: wgpu::VertexState {
81                module: &shader,
82                entry_point: Some("main_vs"),
83                buffers: &[],
84                compilation_options: wgpu::PipelineCompilationOptions::default(),
85            },
86            fragment: Some(wgpu::FragmentState {
87                module: &shader,
88                entry_point: Some("main_fs"),
89                targets: &[Some(wgpu::ColorTargetState {
90                    format: output_format,
91                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
92                    write_mask: wgpu::ColorWrites::ALL,
93                })],
94                compilation_options: wgpu::PipelineCompilationOptions::default(),
95            }),
96            primitive: wgpu::PrimitiveState::default(),
97            depth_stencil: None,
98            multisample: wgpu::MultisampleState::default(),
99            multiview: None,
100            cache: None,
101        });
102
103        Self {
104            pipeline,
105            uniforms_layout,
106            texture_layout,
107        }
108    }
109
110    /// Draw a single textured quad. Allocates a uniform buffer + 2 bind groups
111    /// per call — fine for M0.6 (one quad per frame). M0.9 batches.
112    pub(crate) fn draw(
113        &self,
114        app: &Application,
115        pass: &mut wgpu::RenderPass<'_>,
116        texture: &Texture,
117        model: Mat4,
118        tint: Color,
119    ) {
120        let device = app.device();
121        let uniforms = QuadUniforms {
122            model: model.to_cols_array_2d(),
123            tint: [tint.r, tint.g, tint.b, tint.a],
124        };
125        let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
126            label: Some("wisp::quad uniforms"),
127            contents: bytemuck::bytes_of(&uniforms),
128            usage: wgpu::BufferUsages::UNIFORM,
129        });
130
131        let uniforms_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
132            label: Some("wisp::quad uniforms bg"),
133            layout: &self.uniforms_layout,
134            entries: &[wgpu::BindGroupEntry {
135                binding: 0,
136                resource: buffer.as_entire_binding(),
137            }],
138        });
139
140        let texture_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
141            label: Some("wisp::quad texture bg"),
142            layout: &self.texture_layout,
143            entries: &[
144                wgpu::BindGroupEntry {
145                    binding: 0,
146                    resource: wgpu::BindingResource::TextureView(texture.view()),
147                },
148                wgpu::BindGroupEntry {
149                    binding: 1,
150                    resource: wgpu::BindingResource::Sampler(texture.sampler()),
151                },
152            ],
153        });
154
155        pass.set_pipeline(&self.pipeline);
156        pass.set_bind_group(0, &uniforms_bg, &[]);
157        pass.set_bind_group(1, &texture_bg, &[]);
158        pass.draw(0..6, 0..1);
159    }
160}