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}