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