1use std::any::Any;
27use std::borrow::Cow;
28use std::collections::HashMap;
29use std::ops::Range;
30use std::sync::Arc;
31
32use aetna_core::affine::Affine2;
33use aetna_core::paint::PhysicalScissor;
34use aetna_core::shader::stock_wgsl;
35use aetna_core::surface::{
36 AppTexture, AppTextureBackend, AppTextureId, SurfaceAlpha, SurfaceFormat, next_app_texture_id,
37};
38use aetna_core::tree::Rect;
39
40use bytemuck::{Pod, Zeroable};
41
42const INITIAL_INSTANCE_CAPACITY: usize = 16;
43
44const SURFACE_INSTANCE_ATTRS: [wgpu::VertexAttribute; 3] = wgpu::vertex_attr_array![
45 1 => Float32x4, 2 => Float32x4, 3 => Float32x2, ];
49
50#[repr(C)]
51#[derive(Copy, Clone, Pod, Zeroable, Debug)]
52struct SurfaceInstance {
53 rect: [f32; 4],
54 matrix: [f32; 4],
55 translation: [f32; 2],
56}
57
58pub(crate) struct SurfaceRun {
59 pub texture_idx: usize,
60 pub scissor: Option<PhysicalScissor>,
61 pub alpha: SurfaceAlpha,
62 pub first: u32,
63 pub count: u32,
64}
65
66struct CachedBindGroup {
67 bind_group: wgpu::BindGroup,
68 last_used_frame: u64,
71}
72
73pub(crate) struct SurfacePaint {
74 instances: Vec<SurfaceInstance>,
75 instance_buf: wgpu::Buffer,
76 instance_capacity: usize,
77 runs: Vec<SurfaceRun>,
78
79 pipeline_premul: wgpu::RenderPipeline,
80 pipeline_straight: wgpu::RenderPipeline,
81 pipeline_opaque: wgpu::RenderPipeline,
82 bind_layout: wgpu::BindGroupLayout,
83 sampler: wgpu::Sampler,
84
85 cache: HashMap<u64, CachedBindGroup>,
87 bind_group_lookup: Vec<u64>,
90 frame_counter: u64,
91}
92
93impl SurfacePaint {
94 pub(crate) fn new(
95 device: &wgpu::Device,
96 target_format: wgpu::TextureFormat,
97 sample_count: u32,
98 frame_bind_layout: &wgpu::BindGroupLayout,
99 ) -> Self {
100 let bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
101 label: Some("aetna_wgpu::surface::texture_bind_layout"),
102 entries: &[
103 wgpu::BindGroupLayoutEntry {
104 binding: 0,
105 visibility: wgpu::ShaderStages::FRAGMENT,
106 ty: wgpu::BindingType::Texture {
107 sample_type: wgpu::TextureSampleType::Float { filterable: true },
108 view_dimension: wgpu::TextureViewDimension::D2,
109 multisampled: false,
110 },
111 count: None,
112 },
113 wgpu::BindGroupLayoutEntry {
114 binding: 1,
115 visibility: wgpu::ShaderStages::FRAGMENT,
116 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
117 count: None,
118 },
119 ],
120 });
121
122 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
123 label: Some("aetna_wgpu::surface::pipeline_layout"),
124 bind_group_layouts: &[Some(frame_bind_layout), Some(&bind_layout)],
125 immediate_size: 0,
126 });
127
128 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
129 label: Some("stock::surface"),
130 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(stock_wgsl::SURFACE)),
131 });
132
133 let pipeline_premul = build_pipeline(
134 device,
135 &pipeline_layout,
136 &shader,
137 target_format,
138 sample_count,
139 "fs_premul",
140 premultiplied_blend(),
141 "aetna_wgpu::surface::pipeline_premul",
142 );
143 let pipeline_straight = build_pipeline(
144 device,
145 &pipeline_layout,
146 &shader,
147 target_format,
148 sample_count,
149 "fs_straight",
150 premultiplied_blend(),
151 "aetna_wgpu::surface::pipeline_straight",
152 );
153 let pipeline_opaque = build_pipeline(
154 device,
155 &pipeline_layout,
156 &shader,
157 target_format,
158 sample_count,
159 "fs_opaque",
160 opaque_blend(),
164 "aetna_wgpu::surface::pipeline_opaque",
165 );
166
167 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
168 label: Some("aetna_wgpu::surface::sampler"),
169 address_mode_u: wgpu::AddressMode::ClampToEdge,
170 address_mode_v: wgpu::AddressMode::ClampToEdge,
171 address_mode_w: wgpu::AddressMode::ClampToEdge,
172 mag_filter: wgpu::FilterMode::Linear,
173 min_filter: wgpu::FilterMode::Linear,
174 mipmap_filter: wgpu::MipmapFilterMode::Linear,
175 ..Default::default()
176 });
177
178 let instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
179 label: Some("aetna_wgpu::surface::instance_buf"),
180 size: (INITIAL_INSTANCE_CAPACITY * std::mem::size_of::<SurfaceInstance>()) as u64,
181 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
182 mapped_at_creation: false,
183 });
184
185 Self {
186 instances: Vec::with_capacity(INITIAL_INSTANCE_CAPACITY),
187 instance_buf,
188 instance_capacity: INITIAL_INSTANCE_CAPACITY,
189 runs: Vec::new(),
190 pipeline_premul,
191 pipeline_straight,
192 pipeline_opaque,
193 bind_layout,
194 sampler,
195 cache: HashMap::new(),
196 bind_group_lookup: Vec::new(),
197 frame_counter: 0,
198 }
199 }
200
201 pub(crate) fn frame_begin(&mut self) {
202 self.instances.clear();
203 self.runs.clear();
204 self.bind_group_lookup.clear();
205 self.frame_counter = self.frame_counter.wrapping_add(1);
206 }
207
208 pub(crate) fn record(
209 &mut self,
210 device: &wgpu::Device,
211 rect: Rect,
212 scissor: Option<PhysicalScissor>,
213 texture: &AppTexture,
214 alpha: SurfaceAlpha,
215 transform: Affine2,
216 ) -> Range<usize> {
217 if rect.w <= 0.0 || rect.h <= 0.0 {
218 let start = self.runs.len();
219 return start..start;
220 }
221 let start = self.runs.len();
222 let texture_idx = self.ensure_bind_group(device, texture);
223 let instance = SurfaceInstance {
224 rect: [rect.x, rect.y, rect.w, rect.h],
225 matrix: [transform.a, transform.b, transform.c, transform.d],
226 translation: [transform.tx, transform.ty],
227 };
228 let first = self.instances.len() as u32;
229 self.instances.push(instance);
230 self.runs.push(SurfaceRun {
231 texture_idx,
232 scissor,
233 alpha,
234 first,
235 count: 1,
236 });
237 start..self.runs.len()
238 }
239
240 fn ensure_bind_group(&mut self, device: &wgpu::Device, texture: &AppTexture) -> usize {
241 let id = texture.id().0;
242 if !self.cache.contains_key(&id) {
243 let backend = texture.backend();
244 let wgpu_tex = backend.as_any().downcast_ref::<WgpuAppTexture>().unwrap_or_else(|| {
245 panic!(
246 "AppTexture passed to aetna-wgpu was not constructed by aetna_wgpu::app_texture \
247 (actual backend: {}); mixing backends in one runtime is unsupported",
248 texture.backend_name(),
249 )
250 });
251 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
252 label: Some("aetna_wgpu::surface::bind_group"),
253 layout: &self.bind_layout,
254 entries: &[
255 wgpu::BindGroupEntry {
256 binding: 0,
257 resource: wgpu::BindingResource::TextureView(&wgpu_tex.view),
258 },
259 wgpu::BindGroupEntry {
260 binding: 1,
261 resource: wgpu::BindingResource::Sampler(&self.sampler),
262 },
263 ],
264 });
265 self.cache.insert(
266 id,
267 CachedBindGroup {
268 bind_group,
269 last_used_frame: 0,
270 },
271 );
272 }
273 let entry = self.cache.get_mut(&id).expect("just inserted");
274 entry.last_used_frame = self.frame_counter;
275 if let Some(idx) = self.bind_group_lookup.iter().position(|&i| i == id) {
276 idx
277 } else {
278 self.bind_group_lookup.push(id);
279 self.bind_group_lookup.len() - 1
280 }
281 }
282
283 pub(crate) fn flush(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
284 let frame = self.frame_counter;
285 self.cache.retain(|_, v| v.last_used_frame == frame);
286
287 if self.instances.len() > self.instance_capacity {
288 let new_cap = self.instances.len().next_power_of_two();
289 self.instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
290 label: Some("aetna_wgpu::surface::instance_buf (resized)"),
291 size: (new_cap * std::mem::size_of::<SurfaceInstance>()) as u64,
292 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
293 mapped_at_creation: false,
294 });
295 self.instance_capacity = new_cap;
296 }
297 if !self.instances.is_empty() {
298 queue.write_buffer(&self.instance_buf, 0, bytemuck::cast_slice(&self.instances));
299 }
300 }
301
302 pub(crate) fn run(&self, index: usize) -> &SurfaceRun {
303 &self.runs[index]
304 }
305
306 pub(crate) fn pipeline_for(&self, alpha: SurfaceAlpha) -> &wgpu::RenderPipeline {
307 match alpha {
308 SurfaceAlpha::Premultiplied => &self.pipeline_premul,
309 SurfaceAlpha::Straight => &self.pipeline_straight,
310 SurfaceAlpha::Opaque => &self.pipeline_opaque,
311 }
312 }
313
314 pub(crate) fn instance_buf(&self) -> &wgpu::Buffer {
315 &self.instance_buf
316 }
317
318 pub(crate) fn bind_group_for_run(&self, run: &SurfaceRun) -> &wgpu::BindGroup {
319 let id = self.bind_group_lookup[run.texture_idx];
320 &self
321 .cache
322 .get(&id)
323 .expect("cache entry alive for the frame")
324 .bind_group
325 }
326}
327
328#[allow(clippy::too_many_arguments)]
329fn build_pipeline(
330 device: &wgpu::Device,
331 pipeline_layout: &wgpu::PipelineLayout,
332 shader: &wgpu::ShaderModule,
333 target_format: wgpu::TextureFormat,
334 sample_count: u32,
335 fs_entry: &'static str,
336 blend: Option<wgpu::BlendState>,
337 label: &'static str,
338) -> wgpu::RenderPipeline {
339 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
340 label: Some(label),
341 layout: Some(pipeline_layout),
342 vertex: wgpu::VertexState {
343 module: shader,
344 entry_point: Some("vs_main"),
345 compilation_options: Default::default(),
346 buffers: &[
347 wgpu::VertexBufferLayout {
348 array_stride: (2 * std::mem::size_of::<f32>()) as u64,
349 step_mode: wgpu::VertexStepMode::Vertex,
350 attributes: &[wgpu::VertexAttribute {
351 shader_location: 0,
352 format: wgpu::VertexFormat::Float32x2,
353 offset: 0,
354 }],
355 },
356 wgpu::VertexBufferLayout {
357 array_stride: std::mem::size_of::<SurfaceInstance>() as u64,
358 step_mode: wgpu::VertexStepMode::Instance,
359 attributes: &SURFACE_INSTANCE_ATTRS,
360 },
361 ],
362 },
363 fragment: Some(wgpu::FragmentState {
364 module: shader,
365 entry_point: Some(fs_entry),
366 compilation_options: Default::default(),
367 targets: &[Some(wgpu::ColorTargetState {
368 format: target_format,
369 blend,
370 write_mask: wgpu::ColorWrites::ALL,
371 })],
372 }),
373 primitive: wgpu::PrimitiveState {
374 topology: wgpu::PrimitiveTopology::TriangleStrip,
375 strip_index_format: None,
376 front_face: wgpu::FrontFace::Ccw,
377 cull_mode: None,
378 polygon_mode: wgpu::PolygonMode::Fill,
379 unclipped_depth: false,
380 conservative: false,
381 },
382 depth_stencil: None,
383 multisample: wgpu::MultisampleState {
384 count: sample_count,
385 mask: !0,
386 alpha_to_coverage_enabled: false,
387 },
388 multiview_mask: None,
389 cache: None,
390 })
391}
392
393fn premultiplied_blend() -> Option<wgpu::BlendState> {
394 Some(wgpu::BlendState {
395 color: wgpu::BlendComponent {
396 src_factor: wgpu::BlendFactor::One,
397 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
398 operation: wgpu::BlendOperation::Add,
399 },
400 alpha: wgpu::BlendComponent {
401 src_factor: wgpu::BlendFactor::One,
402 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
403 operation: wgpu::BlendOperation::Add,
404 },
405 })
406}
407
408fn opaque_blend() -> Option<wgpu::BlendState> {
409 Some(wgpu::BlendState {
410 color: wgpu::BlendComponent {
411 src_factor: wgpu::BlendFactor::One,
412 dst_factor: wgpu::BlendFactor::Zero,
413 operation: wgpu::BlendOperation::Add,
414 },
415 alpha: wgpu::BlendComponent {
416 src_factor: wgpu::BlendFactor::One,
417 dst_factor: wgpu::BlendFactor::Zero,
418 operation: wgpu::BlendOperation::Add,
419 },
420 })
421}
422
423#[derive(Debug)]
429pub struct WgpuAppTexture {
430 pub texture: Arc<wgpu::Texture>,
434 pub view: Arc<wgpu::TextureView>,
437 id: AppTextureId,
438 size: (u32, u32),
439 format: SurfaceFormat,
440}
441
442impl AppTextureBackend for WgpuAppTexture {
443 fn id(&self) -> AppTextureId {
444 self.id
445 }
446 fn size_px(&self) -> (u32, u32) {
447 self.size
448 }
449 fn format(&self) -> SurfaceFormat {
450 self.format
451 }
452 fn as_any(&self) -> &dyn Any {
453 self
454 }
455}
456
457pub fn app_texture(texture: Arc<wgpu::Texture>) -> AppTexture {
473 let format = match texture.format() {
474 wgpu::TextureFormat::Rgba8UnormSrgb => SurfaceFormat::Rgba8UnormSrgb,
475 wgpu::TextureFormat::Bgra8UnormSrgb => SurfaceFormat::Bgra8UnormSrgb,
476 wgpu::TextureFormat::Rgba8Unorm => SurfaceFormat::Rgba8Unorm,
477 f => panic!(
478 "aetna_wgpu::app_texture: unsupported texture format {:?} \
479 (expected Rgba8UnormSrgb / Bgra8UnormSrgb / Rgba8Unorm)",
480 f
481 ),
482 };
483 assert!(
484 texture
485 .usage()
486 .contains(wgpu::TextureUsages::TEXTURE_BINDING),
487 "aetna_wgpu::app_texture: source texture must include TEXTURE_BINDING usage (got {:?})",
488 texture.usage(),
489 );
490 assert_eq!(
491 texture.sample_count(),
492 1,
493 "aetna_wgpu::app_texture: source texture must be single-sampled (got sample_count = {})",
494 texture.sample_count(),
495 );
496 let extent = texture.size();
497 let size = (extent.width, extent.height);
498 let view = Arc::new(texture.create_view(&wgpu::TextureViewDescriptor::default()));
499 AppTexture::from_backend(Arc::new(WgpuAppTexture {
500 texture,
501 view,
502 id: next_app_texture_id(),
503 size,
504 format,
505 }))
506}