1use std::collections::{HashMap, HashSet};
4
5use bytemuck::{Pod, Zeroable};
6use wgpu::util::DeviceExt;
7
8use crate::application::Application;
9use crate::blend::BlendMode;
10use crate::render::blend_pipeline::BlendPipelineMap;
11use crate::render::scene_walk::walk_visible_subtree;
12use crate::scene::{Node, NodeId, Stage};
13use crate::texture::Texture;
14
15#[repr(C)]
16#[derive(Clone, Copy, Pod, Zeroable)]
17pub(crate) struct MeshInstance {
18 pub model: [[f32; 4]; 4],
19 pub tint: [f32; 4],
20 pub rotation_y: f32,
21 pub persp_strength: f32,
22 pub _pad: [f32; 2],
23}
24
25const ATTR_LAYOUT: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![
26 0 => Float32x4,
27 1 => Float32x4,
28 2 => Float32x4,
29 3 => Float32x4,
30 4 => Float32x4,
31 5 => Float32,
32 6 => Float32,
33];
34
35pub(crate) struct MeshPipeline {
36 pipelines: BlendPipelineMap,
37 texture_layout: wgpu::BindGroupLayout,
38}
39
40impl MeshPipeline {
41 pub(crate) fn new(app: &Application, output_format: wgpu::TextureFormat) -> Self {
42 let device = app.device();
43 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
44 label: Some("wisp::mesh_perspective"),
45 source: wgpu::ShaderSource::Wgsl(
46 include_str!("../../shaders/mesh_perspective.wgsl").into(),
47 ),
48 });
49
50 let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
51 label: Some("wisp::mesh texture layout"),
52 entries: &[
53 wgpu::BindGroupLayoutEntry {
54 binding: 0,
55 visibility: wgpu::ShaderStages::FRAGMENT,
56 ty: wgpu::BindingType::Texture {
57 sample_type: wgpu::TextureSampleType::Float { filterable: true },
58 view_dimension: wgpu::TextureViewDimension::D2,
59 multisampled: false,
60 },
61 count: None,
62 },
63 wgpu::BindGroupLayoutEntry {
64 binding: 1,
65 visibility: wgpu::ShaderStages::FRAGMENT,
66 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
67 count: None,
68 },
69 ],
70 });
71
72 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
73 label: Some("wisp::mesh pipeline layout"),
74 bind_group_layouts: &[&texture_layout],
75 push_constant_ranges: &[],
76 });
77
78 let pipelines = BlendPipelineMap::new(|blend| {
79 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
80 label: Some("wisp::mesh pipeline"),
81 layout: Some(&pipeline_layout),
82 vertex: wgpu::VertexState {
83 module: &shader,
84 entry_point: Some("main_vs"),
85 buffers: &[wgpu::VertexBufferLayout {
86 array_stride: std::mem::size_of::<MeshInstance>() as wgpu::BufferAddress,
87 step_mode: wgpu::VertexStepMode::Instance,
88 attributes: &ATTR_LAYOUT,
89 }],
90 compilation_options: wgpu::PipelineCompilationOptions::default(),
91 },
92 fragment: Some(wgpu::FragmentState {
93 module: &shader,
94 entry_point: Some("main_fs"),
95 targets: &[Some(wgpu::ColorTargetState {
96 format: output_format,
97 blend: Some(blend),
98 write_mask: wgpu::ColorWrites::ALL,
99 })],
100 compilation_options: wgpu::PipelineCompilationOptions::default(),
101 }),
102 primitive: wgpu::PrimitiveState::default(),
103 depth_stencil: None,
104 multisample: wgpu::MultisampleState::default(),
105 multiview: None,
106 cache: None,
107 })
108 });
109
110 Self {
111 pipelines,
112 texture_layout,
113 }
114 }
115
116 pub(crate) fn draw_stage(
118 &self,
119 app: &Application,
120 pass: &mut wgpu::RenderPass<'_>,
121 stage: &Stage,
122 ) -> (u32, u32) {
123 self.draw_subtree(app, pass, stage, stage.root(), &HashSet::new())
124 }
125
126 pub(crate) fn draw_subtree(
128 &self,
129 app: &Application,
130 pass: &mut wgpu::RenderPass<'_>,
131 stage: &Stage,
132 start: NodeId,
133 exclude: &HashSet<NodeId>,
134 ) -> (u32, u32) {
135 let batches = collect_batches(stage, start, exclude);
136 let mut draw_calls = 0u32;
137 let mut meshes = 0u32;
138
139 for batch in &batches {
140 if batch.instances.is_empty() {
141 continue;
142 }
143 let count = u32::try_from(batch.instances.len()).expect("mesh count fits in u32");
144 meshes = meshes.saturating_add(count);
145
146 let buffer = app
147 .device()
148 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
149 label: Some("wisp::mesh instances"),
150 contents: bytemuck::cast_slice(&batch.instances),
151 usage: wgpu::BufferUsages::VERTEX,
152 });
153 let bg = app.device().create_bind_group(&wgpu::BindGroupDescriptor {
154 label: Some("wisp::mesh texture bg"),
155 layout: &self.texture_layout,
156 entries: &[
157 wgpu::BindGroupEntry {
158 binding: 0,
159 resource: wgpu::BindingResource::TextureView(batch.texture.view()),
160 },
161 wgpu::BindGroupEntry {
162 binding: 1,
163 resource: wgpu::BindingResource::Sampler(batch.texture.sampler()),
164 },
165 ],
166 });
167
168 pass.set_pipeline(self.pipelines.get(batch.blend_mode));
169 pass.set_bind_group(0, &bg, &[]);
170 pass.set_vertex_buffer(0, buffer.slice(..));
171 pass.draw(0..6, 0..count);
172 draw_calls += 1;
173 }
174
175 (draw_calls, meshes)
176 }
177}
178
179struct Batch {
180 texture: Texture,
181 blend_mode: BlendMode,
182 instances: Vec<MeshInstance>,
183}
184
185fn collect_batches(stage: &Stage, start: NodeId, exclude: &HashSet<NodeId>) -> Vec<Batch> {
186 type Key = (usize, BlendMode);
187 let mut grouped: HashMap<Key, (Texture, BlendMode, Vec<MeshInstance>)> = HashMap::new();
188 let mut order: Vec<Key> = Vec::new();
189
190 walk_visible_subtree(stage, start, exclude, |_id, node, world| {
191 let container = node.container();
192 if let Node::Mesh(mesh) = node {
193 let mode = container.blend_mode;
194 let key = (mesh.texture.id(), mode);
195 let entry = grouped.entry(key).or_insert_with(|| {
196 order.push(key);
197 (mesh.texture.clone(), mode, Vec::new())
198 });
199 entry.2.push(MeshInstance {
200 model: world.to_cols_array_2d(),
201 tint: [
202 mesh.tint.r,
203 mesh.tint.g,
204 mesh.tint.b,
205 mesh.tint.a * container.alpha,
206 ],
207 rotation_y: mesh.rotation_y,
208 persp_strength: mesh.perspective_strength,
209 _pad: [0.0, 0.0],
210 });
211 }
212 });
213
214 order
215 .into_iter()
216 .filter_map(|key| {
217 grouped
218 .remove(&key)
219 .map(|(texture, blend_mode, instances)| Batch {
220 texture,
221 blend_mode,
222 instances,
223 })
224 })
225 .collect()
226}