1use std::any::Any;
27use std::borrow::Cow;
28use std::collections::HashMap;
29use std::ops::Range;
30use std::sync::Arc;
31
32use damascene_core::affine::Affine2;
33use damascene_core::paint::PhysicalScissor;
34use damascene_core::shader::stock_wgsl;
35use damascene_core::surface::{
36 AppTexture, AppTextureBackend, AppTextureId, SurfaceAlpha, SurfaceFormat, next_app_texture_id,
37};
38use damascene_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 pipeline_layout: wgpu::PipelineLayout,
92 sample_count: u32,
93
94 cache: HashMap<u64, CachedBindGroup>,
96 bind_group_lookup: Vec<u64>,
99 frame_counter: u64,
100}
101
102impl SurfacePaint {
103 pub(crate) fn new(
104 device: &wgpu::Device,
105 target_format: wgpu::TextureFormat,
106 sample_count: u32,
107 frame_bind_layout: &wgpu::BindGroupLayout,
108 ) -> Self {
109 let bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
110 label: Some("damascene_wgpu::surface::texture_bind_layout"),
111 entries: &[
112 wgpu::BindGroupLayoutEntry {
113 binding: 0,
114 visibility: wgpu::ShaderStages::FRAGMENT,
115 ty: wgpu::BindingType::Texture {
116 sample_type: wgpu::TextureSampleType::Float { filterable: true },
117 view_dimension: wgpu::TextureViewDimension::D2,
118 multisampled: false,
119 },
120 count: None,
121 },
122 wgpu::BindGroupLayoutEntry {
123 binding: 1,
124 visibility: wgpu::ShaderStages::FRAGMENT,
125 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
126 count: None,
127 },
128 ],
129 });
130
131 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
132 label: Some("damascene_wgpu::surface::pipeline_layout"),
133 bind_group_layouts: &[Some(frame_bind_layout), Some(&bind_layout)],
134 immediate_size: 0,
135 });
136
137 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
138 label: Some("stock::surface"),
139 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(stock_wgsl::SURFACE)),
140 });
141
142 let pipeline_premul = build_pipeline(
143 device,
144 &pipeline_layout,
145 &shader,
146 target_format,
147 sample_count,
148 "fs_premul",
149 premultiplied_blend(),
150 "damascene_wgpu::surface::pipeline_premul",
151 );
152 let pipeline_straight = build_pipeline(
153 device,
154 &pipeline_layout,
155 &shader,
156 target_format,
157 sample_count,
158 "fs_straight",
159 premultiplied_blend(),
160 "damascene_wgpu::surface::pipeline_straight",
161 );
162 let pipeline_opaque = build_pipeline(
163 device,
164 &pipeline_layout,
165 &shader,
166 target_format,
167 sample_count,
168 "fs_opaque",
169 opaque_blend(),
173 "damascene_wgpu::surface::pipeline_opaque",
174 );
175
176 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
177 label: Some("damascene_wgpu::surface::sampler"),
178 address_mode_u: wgpu::AddressMode::ClampToEdge,
179 address_mode_v: wgpu::AddressMode::ClampToEdge,
180 address_mode_w: wgpu::AddressMode::ClampToEdge,
181 mag_filter: wgpu::FilterMode::Linear,
182 min_filter: wgpu::FilterMode::Linear,
183 mipmap_filter: wgpu::MipmapFilterMode::Linear,
184 ..Default::default()
185 });
186
187 let instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
188 label: Some("damascene_wgpu::surface::instance_buf"),
189 size: (INITIAL_INSTANCE_CAPACITY * std::mem::size_of::<SurfaceInstance>()) as u64,
190 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
191 mapped_at_creation: false,
192 });
193
194 Self {
195 instances: Vec::with_capacity(INITIAL_INSTANCE_CAPACITY),
196 instance_buf,
197 instance_capacity: INITIAL_INSTANCE_CAPACITY,
198 runs: Vec::new(),
199 pipeline_premul,
200 pipeline_straight,
201 pipeline_opaque,
202 bind_layout,
203 sampler,
204 pipeline_layout,
205 sample_count,
206 cache: HashMap::new(),
207 bind_group_lookup: Vec::new(),
208 frame_counter: 0,
209 }
210 }
211
212 pub(crate) fn set_target_format(
219 &mut self,
220 device: &wgpu::Device,
221 target_format: wgpu::TextureFormat,
222 ) {
223 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
224 label: Some("stock::surface"),
225 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(stock_wgsl::SURFACE)),
226 });
227 self.pipeline_premul = build_pipeline(
228 device,
229 &self.pipeline_layout,
230 &shader,
231 target_format,
232 self.sample_count,
233 "fs_premul",
234 premultiplied_blend(),
235 "damascene_wgpu::surface::pipeline_premul",
236 );
237 self.pipeline_straight = build_pipeline(
238 device,
239 &self.pipeline_layout,
240 &shader,
241 target_format,
242 self.sample_count,
243 "fs_straight",
244 premultiplied_blend(),
245 "damascene_wgpu::surface::pipeline_straight",
246 );
247 self.pipeline_opaque = build_pipeline(
248 device,
249 &self.pipeline_layout,
250 &shader,
251 target_format,
252 self.sample_count,
253 "fs_opaque",
254 opaque_blend(),
255 "damascene_wgpu::surface::pipeline_opaque",
256 );
257 }
258
259 pub(crate) fn frame_begin(&mut self) {
260 self.instances.clear();
261 self.runs.clear();
262 self.bind_group_lookup.clear();
263 self.frame_counter = self.frame_counter.wrapping_add(1);
264 }
265
266 pub(crate) fn record(
267 &mut self,
268 device: &wgpu::Device,
269 rect: Rect,
270 scissor: Option<PhysicalScissor>,
271 texture: &AppTexture,
272 alpha: SurfaceAlpha,
273 transform: Affine2,
274 ) -> Range<usize> {
275 if rect.w <= 0.0 || rect.h <= 0.0 {
276 let start = self.runs.len();
277 return start..start;
278 }
279 let start = self.runs.len();
280 let texture_idx = self.ensure_bind_group(device, texture);
281 let instance = SurfaceInstance {
282 rect: [rect.x, rect.y, rect.w, rect.h],
283 matrix: [transform.a, transform.b, transform.c, transform.d],
284 translation: [transform.tx, transform.ty],
285 };
286 let first = self.instances.len() as u32;
287 self.instances.push(instance);
288 self.runs.push(SurfaceRun {
289 texture_idx,
290 scissor,
291 alpha,
292 first,
293 count: 1,
294 });
295 start..self.runs.len()
296 }
297
298 fn ensure_bind_group(&mut self, device: &wgpu::Device, texture: &AppTexture) -> usize {
299 let id = texture.id().0;
300 if !self.cache.contains_key(&id) {
301 let backend = texture.backend();
302 let wgpu_tex = backend.as_any().downcast_ref::<WgpuAppTexture>().unwrap_or_else(|| {
303 panic!(
304 "AppTexture passed to damascene-wgpu was not constructed by damascene_wgpu::app_texture \
305 (actual backend: {}); mixing backends in one runtime is unsupported",
306 texture.backend_name(),
307 )
308 });
309 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
310 label: Some("damascene_wgpu::surface::bind_group"),
311 layout: &self.bind_layout,
312 entries: &[
313 wgpu::BindGroupEntry {
314 binding: 0,
315 resource: wgpu::BindingResource::TextureView(&wgpu_tex.view),
316 },
317 wgpu::BindGroupEntry {
318 binding: 1,
319 resource: wgpu::BindingResource::Sampler(&self.sampler),
320 },
321 ],
322 });
323 self.cache.insert(
324 id,
325 CachedBindGroup {
326 bind_group,
327 last_used_frame: 0,
328 },
329 );
330 }
331 let entry = self.cache.get_mut(&id).expect("just inserted");
332 entry.last_used_frame = self.frame_counter;
333 if let Some(idx) = self.bind_group_lookup.iter().position(|&i| i == id) {
334 idx
335 } else {
336 self.bind_group_lookup.push(id);
337 self.bind_group_lookup.len() - 1
338 }
339 }
340
341 pub(crate) fn flush(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
342 let frame = self.frame_counter;
343 self.cache.retain(|_, v| v.last_used_frame == frame);
344
345 if self.instances.len() > self.instance_capacity {
346 let new_cap = self.instances.len().next_power_of_two();
347 self.instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
348 label: Some("damascene_wgpu::surface::instance_buf (resized)"),
349 size: (new_cap * std::mem::size_of::<SurfaceInstance>()) as u64,
350 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
351 mapped_at_creation: false,
352 });
353 self.instance_capacity = new_cap;
354 }
355 if !self.instances.is_empty() {
356 queue.write_buffer(&self.instance_buf, 0, bytemuck::cast_slice(&self.instances));
357 }
358 }
359
360 pub(crate) fn run(&self, index: usize) -> &SurfaceRun {
361 &self.runs[index]
362 }
363
364 pub(crate) fn pipeline_for(&self, alpha: SurfaceAlpha) -> &wgpu::RenderPipeline {
365 match alpha {
366 SurfaceAlpha::Premultiplied => &self.pipeline_premul,
367 SurfaceAlpha::Straight => &self.pipeline_straight,
368 SurfaceAlpha::Opaque => &self.pipeline_opaque,
369 }
370 }
371
372 pub(crate) fn instance_buf(&self) -> &wgpu::Buffer {
373 &self.instance_buf
374 }
375
376 pub(crate) fn bind_group_for_run(&self, run: &SurfaceRun) -> &wgpu::BindGroup {
377 let id = self.bind_group_lookup[run.texture_idx];
378 &self
379 .cache
380 .get(&id)
381 .expect("cache entry alive for the frame")
382 .bind_group
383 }
384}
385
386#[allow(clippy::too_many_arguments)]
387fn build_pipeline(
388 device: &wgpu::Device,
389 pipeline_layout: &wgpu::PipelineLayout,
390 shader: &wgpu::ShaderModule,
391 target_format: wgpu::TextureFormat,
392 sample_count: u32,
393 fs_entry: &'static str,
394 blend: Option<wgpu::BlendState>,
395 label: &'static str,
396) -> wgpu::RenderPipeline {
397 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
398 label: Some(label),
399 layout: Some(pipeline_layout),
400 vertex: wgpu::VertexState {
401 module: shader,
402 entry_point: Some("vs_main"),
403 compilation_options: Default::default(),
404 buffers: &[
405 Some(wgpu::VertexBufferLayout {
406 array_stride: (2 * std::mem::size_of::<f32>()) as u64,
407 step_mode: wgpu::VertexStepMode::Vertex,
408 attributes: &[wgpu::VertexAttribute {
409 shader_location: 0,
410 format: wgpu::VertexFormat::Float32x2,
411 offset: 0,
412 }],
413 }),
414 Some(wgpu::VertexBufferLayout {
415 array_stride: std::mem::size_of::<SurfaceInstance>() as u64,
416 step_mode: wgpu::VertexStepMode::Instance,
417 attributes: &SURFACE_INSTANCE_ATTRS,
418 }),
419 ],
420 },
421 fragment: Some(wgpu::FragmentState {
422 module: shader,
423 entry_point: Some(fs_entry),
424 compilation_options: Default::default(),
425 targets: &[Some(wgpu::ColorTargetState {
426 format: target_format,
427 blend,
428 write_mask: wgpu::ColorWrites::ALL,
429 })],
430 }),
431 primitive: wgpu::PrimitiveState {
432 topology: wgpu::PrimitiveTopology::TriangleStrip,
433 strip_index_format: None,
434 front_face: wgpu::FrontFace::Ccw,
435 cull_mode: None,
436 polygon_mode: wgpu::PolygonMode::Fill,
437 unclipped_depth: false,
438 conservative: false,
439 },
440 depth_stencil: None,
441 multisample: wgpu::MultisampleState {
442 count: sample_count,
443 mask: !0,
444 alpha_to_coverage_enabled: false,
445 },
446 multiview_mask: None,
447 cache: None,
448 })
449}
450
451fn premultiplied_blend() -> Option<wgpu::BlendState> {
452 Some(wgpu::BlendState {
453 color: wgpu::BlendComponent {
454 src_factor: wgpu::BlendFactor::One,
455 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
456 operation: wgpu::BlendOperation::Add,
457 },
458 alpha: wgpu::BlendComponent {
459 src_factor: wgpu::BlendFactor::One,
460 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
461 operation: wgpu::BlendOperation::Add,
462 },
463 })
464}
465
466fn opaque_blend() -> Option<wgpu::BlendState> {
467 Some(wgpu::BlendState {
468 color: wgpu::BlendComponent {
469 src_factor: wgpu::BlendFactor::One,
470 dst_factor: wgpu::BlendFactor::Zero,
471 operation: wgpu::BlendOperation::Add,
472 },
473 alpha: wgpu::BlendComponent {
474 src_factor: wgpu::BlendFactor::One,
475 dst_factor: wgpu::BlendFactor::Zero,
476 operation: wgpu::BlendOperation::Add,
477 },
478 })
479}
480
481#[derive(Debug)]
487pub struct WgpuAppTexture {
488 pub texture: Arc<wgpu::Texture>,
492 pub view: Arc<wgpu::TextureView>,
495 id: AppTextureId,
496 size: (u32, u32),
497 format: SurfaceFormat,
498}
499
500impl AppTextureBackend for WgpuAppTexture {
501 fn id(&self) -> AppTextureId {
502 self.id
503 }
504 fn size_px(&self) -> (u32, u32) {
505 self.size
506 }
507 fn format(&self) -> SurfaceFormat {
508 self.format
509 }
510 fn as_any(&self) -> &dyn Any {
511 self
512 }
513}
514
515pub fn app_texture(texture: Arc<wgpu::Texture>) -> AppTexture {
531 let format = match texture.format() {
532 wgpu::TextureFormat::Rgba8UnormSrgb => SurfaceFormat::Rgba8UnormSrgb,
533 wgpu::TextureFormat::Bgra8UnormSrgb => SurfaceFormat::Bgra8UnormSrgb,
534 wgpu::TextureFormat::Rgba8Unorm => SurfaceFormat::Rgba8Unorm,
535 wgpu::TextureFormat::Rgba16Float => SurfaceFormat::Rgba16Float,
536 f => panic!(
537 "damascene_wgpu::app_texture: unsupported texture format {:?} \
538 (expected Rgba8UnormSrgb / Bgra8UnormSrgb / Rgba8Unorm / Rgba16Float)",
539 f
540 ),
541 };
542 assert!(
543 texture
544 .usage()
545 .contains(wgpu::TextureUsages::TEXTURE_BINDING),
546 "damascene_wgpu::app_texture: source texture must include TEXTURE_BINDING usage (got {:?})",
547 texture.usage(),
548 );
549 assert_eq!(
550 texture.sample_count(),
551 1,
552 "damascene_wgpu::app_texture: source texture must be single-sampled (got sample_count = {})",
553 texture.sample_count(),
554 );
555 let extent = texture.size();
556 let size = (extent.width, extent.height);
557 let view = Arc::new(texture.create_view(&wgpu::TextureViewDescriptor::default()));
558 AppTexture::from_backend(Arc::new(WgpuAppTexture {
559 texture,
560 view,
561 id: next_app_texture_id(),
562 size,
563 format,
564 }))
565}
566
567pub struct StreamingTexture {
591 texture: Arc<wgpu::Texture>,
592 handle: AppTexture,
593 format: wgpu::TextureFormat,
594 size: (u32, u32),
595 last_hash: Option<u64>,
596}
597
598impl StreamingTexture {
599 pub fn new(
604 device: &wgpu::Device,
605 format: wgpu::TextureFormat,
606 width: u32,
607 height: u32,
608 ) -> Self {
609 let (texture, handle) = Self::create(device, format, width, height);
610 Self {
611 texture,
612 handle,
613 format,
614 size: (width, height),
615 last_hash: None,
616 }
617 }
618
619 fn create(
620 device: &wgpu::Device,
621 format: wgpu::TextureFormat,
622 width: u32,
623 height: u32,
624 ) -> (Arc<wgpu::Texture>, AppTexture) {
625 let texture = Arc::new(device.create_texture(&wgpu::TextureDescriptor {
626 label: Some("damascene-streaming-texture"),
627 size: wgpu::Extent3d {
628 width: width.max(1),
629 height: height.max(1),
630 depth_or_array_layers: 1,
631 },
632 mip_level_count: 1,
633 sample_count: 1,
634 dimension: wgpu::TextureDimension::D2,
635 format,
636 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
637 view_formats: &[],
638 }));
639 let handle = app_texture(texture.clone());
640 (texture, handle)
641 }
642
643 fn bytes_per_pixel(&self) -> u32 {
646 match self.format {
647 wgpu::TextureFormat::Rgba16Float => 8,
648 _ => 4,
649 }
650 }
651
652 pub fn write_frame(
657 &mut self,
658 device: &wgpu::Device,
659 queue: &wgpu::Queue,
660 width: u32,
661 height: u32,
662 data: &[u8],
663 ) {
664 if (width, height) != self.size {
665 let (texture, handle) = Self::create(device, self.format, width, height);
666 self.texture = texture;
667 self.handle = handle;
668 self.size = (width, height);
669 self.last_hash = None;
670 }
671 let bpp = self.bytes_per_pixel();
672 assert_eq!(
673 data.len() as u64,
674 width as u64 * height as u64 * bpp as u64,
675 "StreamingTexture::write_frame: data length {} != {}x{} * {} bytes/px",
676 data.len(),
677 width,
678 height,
679 bpp,
680 );
681 queue.write_texture(
682 wgpu::TexelCopyTextureInfo {
683 texture: &self.texture,
684 mip_level: 0,
685 origin: wgpu::Origin3d::ZERO,
686 aspect: wgpu::TextureAspect::All,
687 },
688 data,
689 wgpu::TexelCopyBufferLayout {
690 offset: 0,
691 bytes_per_row: Some(width * bpp),
692 rows_per_image: Some(height),
693 },
694 wgpu::Extent3d {
695 width,
696 height,
697 depth_or_array_layers: 1,
698 },
699 );
700 }
701
702 pub fn write_frame_if_changed(
708 &mut self,
709 device: &wgpu::Device,
710 queue: &wgpu::Queue,
711 width: u32,
712 height: u32,
713 data: &[u8],
714 ) -> bool {
715 use std::hash::{Hash, Hasher};
716 let mut hasher = std::collections::hash_map::DefaultHasher::new();
717 (width, height).hash(&mut hasher);
718 data.hash(&mut hasher);
719 let hash = hasher.finish();
720 if self.last_hash == Some(hash) && (width, height) == self.size {
721 return false;
722 }
723 self.write_frame(device, queue, width, height, data);
724 self.last_hash = Some(hash);
725 true
726 }
727
728 pub fn app_texture(&self) -> AppTexture {
733 self.handle.clone()
734 }
735
736 pub fn size(&self) -> (u32, u32) {
738 self.size
739 }
740}