Skip to main content

impeller_hal_vulkan/
sampling.rs

1//! Texture bindings for the image paint.
2//!
3//! # Why every draw binds a texture
4//!
5//! The shader is one program serving every material kind, so it declares the
6//! texture and sampler whether a given draw samples them or not. Vulkan wants
7//! every descriptor a pipeline statically uses to be bound, and "statically
8//! uses" does not care that the branch reading it is unreachable for a solid
9//! fill. The alternatives are a pipeline variant per material kind, which
10//! multiplies the cache by four to avoid one binding, or leaving the descriptor
11//! unbound, which is invalid. So a draw that samples nothing binds a one-pixel
12//! placeholder instead.
13//!
14//! # Why one sampler rather than one per tile mode
15//!
16//! Address modes are a property of the paint, and baking them into samplers
17//! would mean a sampler per combination and a descriptor set per draw that used
18//! a different one. The shader does the wrapping arithmetic itself and the
19//! sampler stays fixed at clamp-to-edge, which also keeps repeat and decal from
20//! depending on filtering behavior at the seam.
21
22use crate::device::VulkanContext;
23use crate::resource::{backend_err, VulkanTexture};
24use ash::vk;
25use impeller_hal::{Batch, Error, Result};
26
27/// The descriptor set layout every pipeline is built against.
28///
29/// The image and the sampler are separate bindings rather than one combined
30/// image sampler, because that is what the shader declares: WGSL separates a
31/// `texture_2d` from a `sampler`, and naga carries the separation into SPIR-V.
32///
33/// The sampler is binding one and every image binding is another number, which
34/// is why the images are not zero through three: binding zero is the texture
35/// this renderer's own shader reads, one is the sampler they all share, and two
36/// upward are the extra textures a caller's program may declare. A layout may
37/// carry bindings a shader never mentions, so the solid pipeline is built
38/// against this unchanged.
39pub fn create_descriptor_layout(device: &ash::Device) -> Result<vk::DescriptorSetLayout> {
40    let mut bindings = vec![
41        vk::DescriptorSetLayoutBinding::default()
42            .binding(0)
43            .descriptor_type(vk::DescriptorType::SAMPLED_IMAGE)
44            .descriptor_count(1)
45            .stage_flags(vk::ShaderStageFlags::FRAGMENT),
46        vk::DescriptorSetLayoutBinding::default()
47            .binding(1)
48            .descriptor_type(vk::DescriptorType::SAMPLER)
49            .descriptor_count(1)
50            .stage_flags(vk::ShaderStageFlags::FRAGMENT),
51    ];
52    for extra in 1..impeller_hal::MAX_EFFECT_TEXTURES {
53        bindings.push(
54            vk::DescriptorSetLayoutBinding::default()
55                .binding(image_binding(extra))
56                .descriptor_type(vk::DescriptorType::SAMPLED_IMAGE)
57                .descriptor_count(1)
58                .stage_flags(vk::ShaderStageFlags::FRAGMENT),
59        );
60    }
61    let info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings);
62    unsafe { device.create_descriptor_set_layout(&info, None) }
63        .map_err(|e| backend_err("create_descriptor_set_layout", e))
64}
65
66/// Which binding number the `index`th texture a program declares occupies.
67///
68/// Zero for the first, so that everything written before several textures
69/// existed keeps its binding; then two upward, the sampler holding one.
70pub fn image_binding(index: usize) -> u32 {
71    if index == 0 {
72        0
73    } else {
74        index as u32 + 1
75    }
76}
77
78/// The one sampler every image draw uses.
79pub fn create_sampler(device: &ash::Device) -> Result<vk::Sampler> {
80    let info = vk::SamplerCreateInfo::default()
81        .mag_filter(vk::Filter::LINEAR)
82        .min_filter(vk::Filter::LINEAR)
83        // Between levels as well as within one, which is what trilinear means
84        // and what `FilterQuality.medium` asks for. It changes nothing for the
85        // draws that do not want it: the shader names the level it reads, and
86        // every path but the mipmapped one names zero.
87        .mipmap_mode(vk::SamplerMipmapMode::LINEAR)
88        // Without this the maximum level of detail defaults to zero and the
89        // sampler clamps every read back to the largest level, which looks
90        // exactly like a chain that was never generated.
91        .max_lod(vk::LOD_CLAMP_NONE)
92        // Clamped in the sampler and wrapped in the shader; see the module
93        // note. Clamping here means a repeat's seam interpolates between the
94        // texels the shader's own coordinate names, rather than across the
95        // image's opposite edge.
96        .address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE)
97        .address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE)
98        .address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE);
99    unsafe { device.create_sampler(&info, None) }.map_err(|e| backend_err("create_sampler", e))
100}
101
102/// Descriptor sets and image views for one submission.
103///
104/// Built per submission and torn down with it, matching the multisample and
105/// stencil buffers: the alternative is a cache keyed by texture identity, whose
106/// invalidation rule has to answer what happens when a caller destroys a
107/// texture a set still points at.
108pub struct Bindings {
109    /// `None` where the batch samples nothing and only the context's
110    /// placeholder set is needed, which costs no allocation at all.
111    pool: Option<vk::DescriptorPool>,
112    views: Vec<vk::ImageView>,
113    /// One set per distinct tuple of slots a draw asks for, and the tuple it
114    /// answers to.
115    ///
116    /// A tuple rather than a slot because a set holds every image a draw reads
117    /// at once, and a caller's program may read several. Ordinary draws all
118    /// have one-element tuples, so this is the same table it was with one more
119    /// dimension nobody but a runtime program uses.
120    sets: Vec<(
121        [Option<u32>; impeller_hal::MAX_EFFECT_TEXTURES],
122        vk::DescriptorSet,
123    )>,
124    /// Owned by the context and outliving any one submission, which is what
125    /// lets a deferred submission use it: its descriptor pool would otherwise
126    /// have to survive until a fence the caller retires whenever it likes.
127    placeholder: vk::DescriptorSet,
128}
129
130impl Bindings {
131    /// Bindings for a batch that samples nothing.
132    pub fn placeholder_only(placeholder: vk::DescriptorSet) -> Self {
133        Self {
134            pool: None,
135            views: Vec::new(),
136            sets: Vec::new(),
137            placeholder,
138        }
139    }
140
141    /// The set a draw's material selects.
142    ///
143    /// A material naming no texture gets the placeholder, which is why this
144    /// cannot fail: the table was checked to cover every slot when it was
145    /// built.
146    pub fn set_for(
147        &self,
148        slots: [Option<u32>; impeller_hal::MAX_EFFECT_TEXTURES],
149    ) -> vk::DescriptorSet {
150        if slots.iter().all(Option::is_none) {
151            return self.placeholder;
152        }
153        self.sets
154            .iter()
155            .find(|(held, _)| *held == slots)
156            .map(|(_, set)| *set)
157            // Every tuple a draw asks for was collected before the sets were
158            // built, so this cannot miss. The placeholder is the answer that
159            // draws something wrong rather than reading a descriptor nobody
160            // wrote, which is the safer of the two if it ever does.
161            .unwrap_or(self.placeholder)
162    }
163
164    pub fn destroy(self, ctx: &VulkanContext) {
165        let device = ctx.raw_device();
166        // SAFETY: the submission that used these was waited on, and destroying
167        // the pool frees every set allocated from it.
168        unsafe {
169            for view in self.views {
170                device.destroy_image_view(view, None);
171            }
172            if let Some(pool) = self.pool {
173                device.destroy_descriptor_pool(pool, None);
174            }
175        }
176    }
177}
178
179/// The pool, view and set for the placeholder texture.
180///
181/// Its own pool rather than a set carved from a submission's, so that its
182/// lifetime is the context's and nothing has to reason about when the last
183/// submission using it finished.
184pub fn create_placeholder_binding(
185    device: &ash::Device,
186    image: vk::Image,
187    layout: vk::DescriptorSetLayout,
188    sampler: vk::Sampler,
189) -> Result<(vk::DescriptorPool, vk::ImageView, vk::DescriptorSet)> {
190    // Every image binding the layout declares, not one: a set allocated from
191    // this pool is a set against that layout, so the pool has to have room for
192    // all of them whether or not this set means to use them.
193    let sizes = [
194        vk::DescriptorPoolSize::default()
195            .ty(vk::DescriptorType::SAMPLED_IMAGE)
196            .descriptor_count(impeller_hal::MAX_EFFECT_TEXTURES as u32),
197        vk::DescriptorPoolSize::default()
198            .ty(vk::DescriptorType::SAMPLER)
199            .descriptor_count(1),
200    ];
201    let pool_info = vk::DescriptorPoolCreateInfo::default()
202        .max_sets(1)
203        .pool_sizes(&sizes);
204    let pool = unsafe { device.create_descriptor_pool(&pool_info, None) }
205        .map_err(|e| backend_err("create_descriptor_pool", e))?;
206
207    let view_info = vk::ImageViewCreateInfo::default()
208        .image(image)
209        .view_type(vk::ImageViewType::TYPE_2D)
210        .format(vk::Format::R8G8B8A8_UNORM)
211        .subresource_range(
212            vk::ImageSubresourceRange::default()
213                .aspect_mask(vk::ImageAspectFlags::COLOR)
214                .level_count(1)
215                .layer_count(1),
216        );
217    let view = match unsafe { device.create_image_view(&view_info, None) } {
218        Ok(view) => view,
219        Err(e) => {
220            unsafe { device.destroy_descriptor_pool(pool, None) };
221            return Err(backend_err("create_image_view", e));
222        }
223    };
224
225    let layouts = [layout];
226    let alloc = vk::DescriptorSetAllocateInfo::default()
227        .descriptor_pool(pool)
228        .set_layouts(&layouts);
229    let set = match unsafe { device.allocate_descriptor_sets(&alloc) } {
230        Ok(sets) => sets[0],
231        Err(e) => {
232            unsafe {
233                device.destroy_image_view(view, None);
234                device.destroy_descriptor_pool(pool, None);
235            }
236            return Err(backend_err("allocate_descriptor_sets", e));
237        }
238    };
239
240    let image_info = [vk::DescriptorImageInfo::default()
241        .image_view(view)
242        .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)];
243    let sampler_info = [vk::DescriptorImageInfo::default().sampler(sampler)];
244    // Every image binding, all of them this one texture. A pipeline must have
245    // each binding it declares bound however unreachable the branch reading it,
246    // and this set is what a draw sampling nothing gets -- so all four point at
247    // the one-pixel white texture rather than three of them at nothing.
248    let mut writes: Vec<vk::WriteDescriptorSet> = (0..impeller_hal::MAX_EFFECT_TEXTURES)
249        .map(|index| {
250            vk::WriteDescriptorSet::default()
251                .dst_set(set)
252                .dst_binding(image_binding(index))
253                .descriptor_type(vk::DescriptorType::SAMPLED_IMAGE)
254                .image_info(&image_info)
255        })
256        .collect();
257    writes.push(
258        vk::WriteDescriptorSet::default()
259            .dst_set(set)
260            .dst_binding(1)
261            .descriptor_type(vk::DescriptorType::SAMPLER)
262            .image_info(&sampler_info),
263    );
264    // SAFETY: the set comes from the pool above and nothing reads it yet.
265    unsafe { device.update_descriptor_sets(&writes, &[]) };
266    Ok((pool, view, set))
267}
268
269/// Build the bindings a batch needs from the table it was given.
270pub fn build(
271    ctx: &mut VulkanContext,
272    batch: &Batch,
273    textures: &[&VulkanTexture],
274) -> Result<Bindings> {
275    // Checked before anything is allocated, so a batch naming a slot nobody
276    // supplied fails without leaving objects behind.
277    for slot in batch.texture_slots() {
278        if slot as usize >= textures.len() {
279            return Err(Error::Backend {
280                backend: "vulkan",
281                detail: format!(
282                    "a draw samples texture slot {slot}, but only {} were supplied",
283                    textures.len()
284                ),
285            });
286        }
287    }
288
289    let placeholder = ctx.placeholder_set()?;
290    if textures.is_empty() {
291        return Ok(Bindings::placeholder_only(placeholder));
292    }
293
294    // Built before the borrow below, since each of these creates its object on
295    // first use and so needs the context mutably.
296    let layout = ctx.descriptor_layout()?;
297    let sampler = ctx.sampler()?;
298    let device = ctx.raw_device().clone();
299
300    // Every distinct tuple the batch asks for. An ordinary draw contributes a
301    // one-element tuple, so a batch that samples nothing but images has one per
302    // image exactly as it did; a batch with a program reading two contributes
303    // that pair as well.
304    let mut tuples: Vec<[Option<u32>; impeller_hal::MAX_EFFECT_TEXTURES]> = Vec::new();
305    for draw in batch.draws() {
306        let slots = draw.material.texture_slots();
307        if slots.iter().all(Option::is_none) {
308            continue;
309        }
310        if !tuples.contains(&slots) {
311            tuples.push(slots);
312        }
313    }
314    if tuples.is_empty() {
315        return Ok(Bindings::placeholder_only(placeholder));
316    }
317
318    let count = tuples.len() as u32;
319    let sizes = [
320        vk::DescriptorPoolSize::default()
321            .ty(vk::DescriptorType::SAMPLED_IMAGE)
322            .descriptor_count(count * impeller_hal::MAX_EFFECT_TEXTURES as u32),
323        vk::DescriptorPoolSize::default()
324            .ty(vk::DescriptorType::SAMPLER)
325            .descriptor_count(count),
326    ];
327    let pool_info = vk::DescriptorPoolCreateInfo::default()
328        .max_sets(count)
329        .pool_sizes(&sizes);
330    let pool = unsafe { device.create_descriptor_pool(&pool_info, None) }
331        .map_err(|e| backend_err("create_descriptor_pool", e))?;
332
333    let mut bindings = Bindings {
334        pool: Some(pool),
335        views: Vec::new(),
336        sets: Vec::new(),
337        placeholder,
338    };
339
340    let layouts = vec![layout; count as usize];
341    let alloc = vk::DescriptorSetAllocateInfo::default()
342        .descriptor_pool(pool)
343        .set_layouts(&layouts);
344    let raw_sets = match unsafe { device.allocate_descriptor_sets(&alloc) } {
345        Ok(sets) => sets,
346        Err(e) => {
347            bindings.destroy(ctx);
348            return Err(backend_err("allocate_descriptor_sets", e));
349        }
350    };
351
352    // One view per supplied texture, reused by every set that names it. Views
353    // are per image rather than per set because a set is a tuple of images and
354    // two tuples naming the same texture want the same view.
355    let mut views: Vec<vk::ImageView> = Vec::with_capacity(textures.len());
356    for texture in textures {
357        let view_info = vk::ImageViewCreateInfo::default()
358            .image(texture.raw_image())
359            .view_type(vk::ImageViewType::TYPE_2D)
360            .format(crate::resource::vk_format(texture.format()))
361            .subresource_range(
362                vk::ImageSubresourceRange::default()
363                    .aspect_mask(vk::ImageAspectFlags::COLOR)
364                    // Whatever the image has. A view over one level of a
365                    // mipmapped image is a view a sampler cannot minify
366                    // through, and the failure is silent -- the chain is there,
367                    // the sampler is willing, and every read still lands on the
368                    // largest level.
369                    .level_count(vk::REMAINING_MIP_LEVELS)
370                    .layer_count(1),
371            );
372        match unsafe { device.create_image_view(&view_info, None) } {
373            Ok(view) => views.push(view),
374            Err(e) => {
375                bindings.views = views;
376                bindings.destroy(ctx);
377                return Err(backend_err("create_image_view", e));
378            }
379        }
380    }
381    bindings.views = views;
382
383    // A binding the tuple leaves empty still has to be written, because a
384    // pipeline must have every binding it declares bound however unreachable
385    // the branch reading it. The context's placeholder view is what goes there
386    // -- the same one-pixel white texture a solid fill binds.
387    let blank = ctx.placeholder_view()?;
388    for (index, tuple) in tuples.iter().enumerate() {
389        let set = raw_sets[index];
390        let mut image_infos = Vec::with_capacity(impeller_hal::MAX_EFFECT_TEXTURES);
391        for slot in tuple.iter() {
392            let view = match slot {
393                Some(slot) => bindings.views[*slot as usize],
394                None => blank,
395            };
396            image_infos.push([vk::DescriptorImageInfo::default()
397                .image_view(view)
398                .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)]);
399        }
400        let sampler_info = [vk::DescriptorImageInfo::default().sampler(sampler)];
401        let mut writes: Vec<vk::WriteDescriptorSet> = image_infos
402            .iter()
403            .enumerate()
404            .map(|(position, info)| {
405                vk::WriteDescriptorSet::default()
406                    .dst_set(set)
407                    .dst_binding(image_binding(position))
408                    .descriptor_type(vk::DescriptorType::SAMPLED_IMAGE)
409                    .image_info(info)
410            })
411            .collect();
412        writes.push(
413            vk::WriteDescriptorSet::default()
414                .dst_set(set)
415                .dst_binding(1)
416                .descriptor_type(vk::DescriptorType::SAMPLER)
417                .image_info(&sampler_info),
418        );
419        // SAFETY: the sets come from the pool above, the views outlive the
420        // submission that reads them, and every binding written is one the
421        // layout declares.
422        unsafe { device.update_descriptor_sets(&writes, &[]) };
423        bindings.sets.push((*tuple, set));
424    }
425
426    Ok(bindings)
427}
428
429/// Move every texture a batch samples into the layout a shader reads from.
430///
431/// Sampled textures are usually render targets that a previous pass left in a
432/// color-attachment layout, or fresh uploads sitting in a transfer layout.
433/// Reading either without a transition is undefined, and on a tiled or
434/// compressed-framebuffer device it is undefined in the way that produces a
435/// plausible but wrong image rather than nothing at all.
436pub fn transition_for_sampling(
437    device: &ash::Device,
438    cmd: vk::CommandBuffer,
439    textures: &[&VulkanTexture],
440) {
441    for texture in textures {
442        crate::resource::transition(
443            device,
444            cmd,
445            texture.raw_image(),
446            texture.layout(),
447            vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
448        );
449        texture.set_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
450    }
451}