Skip to main content

damascene_ash/
surface.rs

1//! GPU compositing for app-owned [`AppTexture`]s on the ash backend.
2//!
3//! The host owns the Vulkan image and image view. `damascene-ash` only
4//! caches descriptor sets that sample the supplied view while recording
5//! Damascene's normal paint stream.
6
7use std::any::Any;
8use std::collections::HashMap;
9use std::ffi::CString;
10use std::ops::Range;
11use std::sync::Arc;
12
13use ash::vk;
14use bytemuck::{Pod, Zeroable};
15use damascene_core::affine::Affine2;
16use damascene_core::paint::PhysicalScissor;
17use damascene_core::shader::stock_wgsl;
18use damascene_core::surface::{
19    AppTexture, AppTextureBackend, AppTextureId, SurfaceAlpha, SurfaceFormat, next_app_texture_id,
20};
21use damascene_core::tree::Rect;
22use gpu_allocator::MemoryLocation;
23use gpu_allocator::vulkan::Allocator;
24
25use crate::buffer::GpuBuffer;
26use crate::naga_compile::wgsl_to_spirv;
27use crate::runner::{Error, Result, TargetInfo};
28
29const INITIAL_SURFACE_INSTANCE_CAPACITY: usize = 16;
30
31#[repr(C)]
32#[derive(Copy, Clone, Pod, Zeroable, Debug)]
33struct SurfaceInstance {
34    rect: [f32; 4],
35    matrix: [f32; 4],
36    translation: [f32; 2],
37}
38
39pub(crate) struct SurfaceRun {
40    pub texture_idx: usize,
41    pub scissor: Option<PhysicalScissor>,
42    pub alpha: SurfaceAlpha,
43    pub first: u32,
44    pub count: u32,
45}
46
47struct CachedDescriptor {
48    descriptor_set: vk::DescriptorSet,
49    last_used_frame: u64,
50}
51
52pub(crate) struct SurfacePaint {
53    instances: Vec<SurfaceInstance>,
54    instance_buf: GpuBuffer,
55    instance_capacity: usize,
56    runs: Vec<SurfaceRun>,
57    texture_set_layout: vk::DescriptorSetLayout,
58    pipeline_layout: vk::PipelineLayout,
59    pipeline_premul: vk::Pipeline,
60    pipeline_straight: vk::Pipeline,
61    pipeline_opaque: vk::Pipeline,
62    descriptor_pool: vk::DescriptorPool,
63    sampler: vk::Sampler,
64    cache: HashMap<u64, CachedDescriptor>,
65    bind_lookup: Vec<u64>,
66    frame_counter: u64,
67}
68
69impl SurfacePaint {
70    pub(crate) fn new(
71        device: &ash::Device,
72        allocator: &mut Allocator,
73        frame_set_layout: vk::DescriptorSetLayout,
74        target: TargetInfo,
75    ) -> Result<Self> {
76        let texture_set_layout = create_texture_set_layout(device)?;
77        let layouts = [frame_set_layout, texture_set_layout];
78        let pipeline_layout = create_pipeline_layout(device, &layouts)?;
79        let pipeline_premul = build_surface_pipeline(
80            device,
81            pipeline_layout,
82            target,
83            "stock::surface::premul",
84            "fs_premul",
85            SurfaceAlpha::Premultiplied,
86        )?;
87        let pipeline_straight = build_surface_pipeline(
88            device,
89            pipeline_layout,
90            target,
91            "stock::surface::straight",
92            "fs_straight",
93            SurfaceAlpha::Straight,
94        )?;
95        let pipeline_opaque = build_surface_pipeline(
96            device,
97            pipeline_layout,
98            target,
99            "stock::surface::opaque",
100            "fs_opaque",
101            SurfaceAlpha::Opaque,
102        )?;
103        let pool_sizes = [
104            vk::DescriptorPoolSize {
105                ty: vk::DescriptorType::SAMPLED_IMAGE,
106                descriptor_count: 256,
107            },
108            vk::DescriptorPoolSize {
109                ty: vk::DescriptorType::SAMPLER,
110                descriptor_count: 256,
111            },
112        ];
113        let pool_info = vk::DescriptorPoolCreateInfo::default()
114            .flags(vk::DescriptorPoolCreateFlags::FREE_DESCRIPTOR_SET)
115            .max_sets(256)
116            .pool_sizes(&pool_sizes);
117        let descriptor_pool = unsafe { device.create_descriptor_pool(&pool_info, None) }?;
118        let sampler_info = vk::SamplerCreateInfo::default()
119            .mag_filter(vk::Filter::LINEAR)
120            .min_filter(vk::Filter::LINEAR)
121            .mipmap_mode(vk::SamplerMipmapMode::LINEAR)
122            .address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE)
123            .address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE)
124            .address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE);
125        let sampler = unsafe { device.create_sampler(&sampler_info, None) }?;
126        let instance_capacity = INITIAL_SURFACE_INSTANCE_CAPACITY;
127        let instance_buf = GpuBuffer::new(
128            device,
129            allocator,
130            "damascene_ash::surface_instances",
131            (instance_capacity * std::mem::size_of::<SurfaceInstance>()) as vk::DeviceSize,
132            vk::BufferUsageFlags::VERTEX_BUFFER,
133            MemoryLocation::CpuToGpu,
134        )?;
135        Ok(Self {
136            instances: Vec::new(),
137            instance_buf,
138            instance_capacity,
139            runs: Vec::new(),
140            texture_set_layout,
141            pipeline_layout,
142            pipeline_premul,
143            pipeline_straight,
144            pipeline_opaque,
145            descriptor_pool,
146            sampler,
147            cache: HashMap::new(),
148            bind_lookup: Vec::new(),
149            frame_counter: 0,
150        })
151    }
152
153    pub(crate) fn frame_begin(&mut self) {
154        self.instances.clear();
155        self.runs.clear();
156        self.bind_lookup.clear();
157        self.frame_counter = self.frame_counter.wrapping_add(1);
158    }
159
160    pub(crate) fn record(
161        &mut self,
162        device: &ash::Device,
163        rect: Rect,
164        scissor: Option<PhysicalScissor>,
165        texture: &AppTexture,
166        alpha: SurfaceAlpha,
167        transform: Affine2,
168    ) -> Result<Range<usize>> {
169        let start = self.runs.len();
170        if rect.w <= 0.0 || rect.h <= 0.0 {
171            return Ok(start..start);
172        }
173        let texture_idx = self.ensure_descriptor(device, texture)?;
174        let first = self.instances.len() as u32;
175        self.instances.push(SurfaceInstance {
176            rect: [rect.x, rect.y, rect.w, rect.h],
177            matrix: [transform.a, transform.b, transform.c, transform.d],
178            translation: [transform.tx, transform.ty],
179        });
180        self.runs.push(SurfaceRun {
181            texture_idx,
182            scissor,
183            alpha,
184            first,
185            count: 1,
186        });
187        Ok(start..self.runs.len())
188    }
189
190    pub(crate) fn flush(&mut self, device: &ash::Device, allocator: &mut Allocator) -> Result<()> {
191        let mut stale = Vec::new();
192        self.cache.retain(|_, cached| {
193            let keep = cached.last_used_frame == self.frame_counter;
194            if !keep {
195                stale.push(cached.descriptor_set);
196            }
197            keep
198        });
199        if !stale.is_empty() {
200            unsafe {
201                device.free_descriptor_sets(self.descriptor_pool, &stale)?;
202            }
203        }
204        self.ensure_instance_capacity(device, allocator)?;
205        self.instance_buf
206            .write_bytes(bytemuck::cast_slice(&self.instances))?;
207        Ok(())
208    }
209
210    fn ensure_descriptor(&mut self, device: &ash::Device, texture: &AppTexture) -> Result<usize> {
211        let backend = texture
212            .backend()
213            .as_any()
214            .downcast_ref::<AshAppTexture>()
215            .unwrap_or_else(|| {
216                panic!(
217                    "damascene-ash expected AshAppTexture, got {}",
218                    texture.backend_name()
219                )
220            });
221        let id = backend.id.0;
222        if !self.cache.contains_key(&id) {
223            let cached = self.create_descriptor(device, backend)?;
224            self.cache.insert(id, cached);
225        }
226        self.cache
227            .get_mut(&id)
228            .expect("just inserted")
229            .last_used_frame = self.frame_counter;
230        if let Some(idx) = self.bind_lookup.iter().position(|&h| h == id) {
231            Ok(idx)
232        } else {
233            self.bind_lookup.push(id);
234            Ok(self.bind_lookup.len() - 1)
235        }
236    }
237
238    fn create_descriptor(
239        &self,
240        device: &ash::Device,
241        texture: &AshAppTexture,
242    ) -> Result<CachedDescriptor> {
243        let set_layouts = [self.texture_set_layout];
244        let allocate_info = vk::DescriptorSetAllocateInfo::default()
245            .descriptor_pool(self.descriptor_pool)
246            .set_layouts(&set_layouts);
247        let descriptor_set = unsafe { device.allocate_descriptor_sets(&allocate_info) }?[0];
248        let image_info = vk::DescriptorImageInfo::default()
249            .image_view(texture.view)
250            .image_layout(texture.image_layout);
251        let sampler_info = vk::DescriptorImageInfo::default().sampler(self.sampler);
252        let writes = [
253            vk::WriteDescriptorSet::default()
254                .dst_set(descriptor_set)
255                .dst_binding(0)
256                .descriptor_type(vk::DescriptorType::SAMPLED_IMAGE)
257                .image_info(std::slice::from_ref(&image_info)),
258            vk::WriteDescriptorSet::default()
259                .dst_set(descriptor_set)
260                .dst_binding(1)
261                .descriptor_type(vk::DescriptorType::SAMPLER)
262                .image_info(std::slice::from_ref(&sampler_info)),
263        ];
264        unsafe {
265            device.update_descriptor_sets(&writes, &[]);
266        }
267        Ok(CachedDescriptor {
268            descriptor_set,
269            last_used_frame: self.frame_counter,
270        })
271    }
272
273    fn ensure_instance_capacity(
274        &mut self,
275        device: &ash::Device,
276        allocator: &mut Allocator,
277    ) -> Result<()> {
278        if self.instances.len() <= self.instance_capacity {
279            return Ok(());
280        }
281        let mut next = self.instance_capacity.max(1);
282        while next < self.instances.len() {
283            next *= 2;
284        }
285        unsafe {
286            self.instance_buf.destroy(device, allocator);
287        }
288        self.instance_buf = GpuBuffer::new(
289            device,
290            allocator,
291            "damascene_ash::surface_instances",
292            (next * std::mem::size_of::<SurfaceInstance>()) as vk::DeviceSize,
293            vk::BufferUsageFlags::VERTEX_BUFFER,
294            MemoryLocation::CpuToGpu,
295        )?;
296        self.instance_capacity = next;
297        Ok(())
298    }
299
300    pub(crate) fn run(&self, index: usize) -> &SurfaceRun {
301        &self.runs[index]
302    }
303
304    pub(crate) fn pipeline_for(&self, alpha: SurfaceAlpha) -> vk::Pipeline {
305        match alpha {
306            SurfaceAlpha::Premultiplied => self.pipeline_premul,
307            SurfaceAlpha::Straight => self.pipeline_straight,
308            SurfaceAlpha::Opaque => self.pipeline_opaque,
309        }
310    }
311
312    pub(crate) fn pipeline_layout(&self) -> vk::PipelineLayout {
313        self.pipeline_layout
314    }
315
316    pub(crate) fn instance_buffer(&self) -> vk::Buffer {
317        self.instance_buf.buffer
318    }
319
320    pub(crate) fn descriptor_for_run(&self, run: &SurfaceRun) -> vk::DescriptorSet {
321        let id = self.bind_lookup[run.texture_idx];
322        self.cache
323            .get(&id)
324            .expect("cache entry alive for frame")
325            .descriptor_set
326    }
327
328    pub(crate) unsafe fn destroy(&mut self, device: &ash::Device, allocator: &mut Allocator) {
329        unsafe {
330            self.instance_buf.destroy(device, allocator);
331            device.destroy_pipeline(self.pipeline_premul, None);
332            device.destroy_pipeline(self.pipeline_straight, None);
333            device.destroy_pipeline(self.pipeline_opaque, None);
334            device.destroy_pipeline_layout(self.pipeline_layout, None);
335            device.destroy_sampler(self.sampler, None);
336            device.destroy_descriptor_pool(self.descriptor_pool, None);
337            device.destroy_descriptor_set_layout(self.texture_set_layout, None);
338        }
339    }
340}
341
342/// Host-owned ash texture that Damascene can sample during paint.
343///
344/// The image must be a single-sampled 2D image created with
345/// `vk::ImageUsageFlags::SAMPLED`, and the host must keep both the
346/// image and view alive while this wrapper may be painted. Before Damascene
347/// draw commands execute, the host must transition the image to the
348/// same readable layout recorded in this wrapper.
349#[derive(Clone, Copy, Debug)]
350pub struct AshAppTexture {
351    pub image: vk::Image,
352    pub view: vk::ImageView,
353    pub image_layout: vk::ImageLayout,
354    id: AppTextureId,
355    size: (u32, u32),
356    format: SurfaceFormat,
357}
358
359/// Wrap a host-owned Vulkan image/view pair as an Damascene app texture.
360///
361/// `damascene-ash` cannot inspect raw ash image creation flags, so this
362/// constructor validates only the format and extent. The host remains
363/// responsible for usage flags, sample count, synchronization, layout,
364/// and lifetime. This shortcut assumes the image will be in
365/// `SHADER_READ_ONLY_OPTIMAL` when Damascene samples it; use
366/// [`app_texture_with_layout`] if your frame graph leaves the image in
367/// another shader-readable layout such as `GENERAL`.
368pub fn app_texture(
369    image: vk::Image,
370    view: vk::ImageView,
371    format: vk::Format,
372    extent: vk::Extent2D,
373) -> AppTexture {
374    app_texture_with_layout(
375        image,
376        view,
377        format,
378        extent,
379        vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
380    )
381}
382
383/// Wrap a host-owned Vulkan image/view pair with an explicit sampled
384/// image layout.
385///
386/// The supplied layout is written into the descriptor set. The host must
387/// make sure the image is actually in that layout when Damascene draw
388/// commands execute.
389pub fn app_texture_with_layout(
390    image: vk::Image,
391    view: vk::ImageView,
392    format: vk::Format,
393    extent: vk::Extent2D,
394    image_layout: vk::ImageLayout,
395) -> AppTexture {
396    let surface_format = match format {
397        vk::Format::R8G8B8A8_SRGB => SurfaceFormat::Rgba8UnormSrgb,
398        vk::Format::B8G8R8A8_SRGB => SurfaceFormat::Bgra8UnormSrgb,
399        vk::Format::R8G8B8A8_UNORM => SurfaceFormat::Rgba8Unorm,
400        vk::Format::R16G16B16A16_SFLOAT => SurfaceFormat::Rgba16Float,
401        _ => panic!("unsupported damascene-ash app texture format: {format:?}"),
402    };
403    assert!(
404        extent.width > 0 && extent.height > 0,
405        "damascene-ash app textures must have non-zero extent"
406    );
407    AppTexture::from_backend(Arc::new(AshAppTexture {
408        image,
409        view,
410        image_layout,
411        id: next_app_texture_id(),
412        size: (extent.width, extent.height),
413        format: surface_format,
414    }))
415}
416
417impl AppTextureBackend for AshAppTexture {
418    fn id(&self) -> AppTextureId {
419        self.id
420    }
421
422    fn size_px(&self) -> (u32, u32) {
423        self.size
424    }
425
426    fn format(&self) -> SurfaceFormat {
427        self.format
428    }
429
430    fn as_any(&self) -> &dyn Any {
431        self
432    }
433}
434
435fn create_texture_set_layout(device: &ash::Device) -> Result<vk::DescriptorSetLayout> {
436    let bindings = [
437        vk::DescriptorSetLayoutBinding::default()
438            .binding(0)
439            .descriptor_type(vk::DescriptorType::SAMPLED_IMAGE)
440            .descriptor_count(1)
441            .stage_flags(vk::ShaderStageFlags::FRAGMENT),
442        vk::DescriptorSetLayoutBinding::default()
443            .binding(1)
444            .descriptor_type(vk::DescriptorType::SAMPLER)
445            .descriptor_count(1)
446            .stage_flags(vk::ShaderStageFlags::FRAGMENT),
447    ];
448    let info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings);
449    unsafe { device.create_descriptor_set_layout(&info, None) }.map_err(Into::into)
450}
451
452fn create_pipeline_layout(
453    device: &ash::Device,
454    layouts: &[vk::DescriptorSetLayout],
455) -> Result<vk::PipelineLayout> {
456    let info = vk::PipelineLayoutCreateInfo::default().set_layouts(layouts);
457    unsafe { device.create_pipeline_layout(&info, None) }.map_err(Into::into)
458}
459
460fn build_surface_pipeline(
461    device: &ash::Device,
462    pipeline_layout: vk::PipelineLayout,
463    target: TargetInfo,
464    name: &str,
465    fragment_entry: &str,
466    alpha: SurfaceAlpha,
467) -> Result<vk::Pipeline> {
468    let words = wgsl_to_spirv(name, stock_wgsl::SURFACE)?;
469    let shader_info = vk::ShaderModuleCreateInfo::default().code(&words);
470    let shader = unsafe { device.create_shader_module(&shader_info, None) }?;
471    let result = build_pipeline_with_module(
472        device,
473        pipeline_layout,
474        target,
475        shader,
476        name,
477        fragment_entry,
478        alpha,
479    );
480    unsafe {
481        device.destroy_shader_module(shader, None);
482    }
483    result
484}
485
486fn build_pipeline_with_module(
487    device: &ash::Device,
488    pipeline_layout: vk::PipelineLayout,
489    target: TargetInfo,
490    shader: vk::ShaderModule,
491    name: &str,
492    fragment_entry: &str,
493    alpha: SurfaceAlpha,
494) -> Result<vk::Pipeline> {
495    let vs_main = CString::new("vs_main").expect("static string has no nul");
496    let fs_main = CString::new(fragment_entry).expect("static fragment entry has no nul");
497    let stages = [
498        vk::PipelineShaderStageCreateInfo::default()
499            .stage(vk::ShaderStageFlags::VERTEX)
500            .module(shader)
501            .name(&vs_main),
502        vk::PipelineShaderStageCreateInfo::default()
503            .stage(vk::ShaderStageFlags::FRAGMENT)
504            .module(shader)
505            .name(&fs_main),
506    ];
507    let bindings = [
508        vk::VertexInputBindingDescription {
509            binding: 0,
510            stride: (2 * std::mem::size_of::<f32>()) as u32,
511            input_rate: vk::VertexInputRate::VERTEX,
512        },
513        vk::VertexInputBindingDescription {
514            binding: 1,
515            stride: std::mem::size_of::<SurfaceInstance>() as u32,
516            input_rate: vk::VertexInputRate::INSTANCE,
517        },
518    ];
519    let attrs = [
520        attr(0, 0, 0, vk::Format::R32G32_SFLOAT),
521        attr(1, 1, 0, vk::Format::R32G32B32A32_SFLOAT),
522        attr(2, 1, 16, vk::Format::R32G32B32A32_SFLOAT),
523        attr(3, 1, 32, vk::Format::R32G32_SFLOAT),
524    ];
525    let vertex_input = vk::PipelineVertexInputStateCreateInfo::default()
526        .vertex_binding_descriptions(&bindings)
527        .vertex_attribute_descriptions(&attrs);
528    let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
529        .topology(vk::PrimitiveTopology::TRIANGLE_STRIP);
530    let viewport_state = vk::PipelineViewportStateCreateInfo::default()
531        .viewport_count(1)
532        .scissor_count(1);
533    let rasterization = vk::PipelineRasterizationStateCreateInfo::default()
534        .polygon_mode(vk::PolygonMode::FILL)
535        .cull_mode(vk::CullModeFlags::empty())
536        .front_face(vk::FrontFace::COUNTER_CLOCKWISE)
537        .line_width(1.0);
538    let multisample = vk::PipelineMultisampleStateCreateInfo::default()
539        .rasterization_samples(target.sample_count)
540        .sample_shading_enable(target.sample_count != vk::SampleCountFlags::TYPE_1)
541        .min_sample_shading(1.0);
542    let blend_attachment = blend_attachment(alpha);
543    let blend_attachments = [blend_attachment];
544    let color_blend =
545        vk::PipelineColorBlendStateCreateInfo::default().attachments(&blend_attachments);
546    let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
547    let dynamic_state =
548        vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
549    let color_formats = [target.format];
550    let mut rendering =
551        vk::PipelineRenderingCreateInfo::default().color_attachment_formats(&color_formats);
552    let info = vk::GraphicsPipelineCreateInfo::default()
553        .stages(&stages)
554        .vertex_input_state(&vertex_input)
555        .input_assembly_state(&input_assembly)
556        .viewport_state(&viewport_state)
557        .rasterization_state(&rasterization)
558        .multisample_state(&multisample)
559        .color_blend_state(&color_blend)
560        .dynamic_state(&dynamic_state)
561        .layout(pipeline_layout)
562        .push_next(&mut rendering);
563    let pipelines =
564        unsafe { device.create_graphics_pipelines(vk::PipelineCache::null(), &[info], None) }
565            .map_err(|(_pipelines, err)| Error::Vulkan {
566                op: "create_graphics_pipelines",
567                result: err,
568            })?;
569    pipelines
570        .into_iter()
571        .next()
572        .ok_or(Error::PipelineCreationReturnedEmpty {
573            name: name.to_string(),
574        })
575}
576
577fn blend_attachment(alpha: SurfaceAlpha) -> vk::PipelineColorBlendAttachmentState {
578    let (src, dst) = match alpha {
579        SurfaceAlpha::Premultiplied | SurfaceAlpha::Straight => {
580            (vk::BlendFactor::ONE, vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
581        }
582        SurfaceAlpha::Opaque => (vk::BlendFactor::ONE, vk::BlendFactor::ZERO),
583    };
584    vk::PipelineColorBlendAttachmentState::default()
585        .blend_enable(true)
586        .src_color_blend_factor(src)
587        .dst_color_blend_factor(dst)
588        .color_blend_op(vk::BlendOp::ADD)
589        .src_alpha_blend_factor(src)
590        .dst_alpha_blend_factor(dst)
591        .alpha_blend_op(vk::BlendOp::ADD)
592        .color_write_mask(
593            vk::ColorComponentFlags::R
594                | vk::ColorComponentFlags::G
595                | vk::ColorComponentFlags::B
596                | vk::ColorComponentFlags::A,
597        )
598}
599
600fn attr(
601    location: u32,
602    binding: u32,
603    offset: u32,
604    format: vk::Format,
605) -> vk::VertexInputAttributeDescription {
606    vk::VertexInputAttributeDescription {
607        location,
608        binding,
609        format,
610        offset,
611    }
612}