Skip to main content

gpu/
vk_res.rs

1// vk_res -- Vulkan resources, bindings and pipelines
2//
3//   device.create_buffer(&BufferDesc) -> Buffer        copies to and from it always allowed;
4//        Upload / Readback buffers are mapped for their lifetime: write(off, &[T]) /
5//        read(off, &mut [T]) (non-coherent memory flushed / invalidated by atom)
6//   device.create_texture(&TextureDesc) -> Texture     optimal tiling, starts in Layout::
7//        Undefined; its default view covers everything (2D, 2D array, cube, cube array, 3D);
8//        texture.create_view(&ViewDesc) -> TextureView a range of mips and layers
9//   device.create_sampler(&SamplerDesc) -> Sampler
10//   ShaderModule::from_spirv(&device, &[u32])          SPIR-V up to 1.3 (Vulkan 1.1), 1.5
11//        (1.2), 1.6 (1.3); the header and the instruction lengths are checked, the code is the
12//        driver's; entry points and specialization constants are read (shader::reflect)
13//   device.create_bind_group_layout(&[Binding])        descriptor set layout (one descriptor
14//        per binding)
15//   device.create_pipeline_layout(&PipelineLayoutDesc) set g = groups[g]; push constants as one
16//        range for every stage (VK_SHADER_STAGE_ALL)
17//   device.create_bind_group(&BindGroupDesc)           a descriptor set from the device's pools
18//        (freed individually); it keeps its resources alive
19//   device.create_bindless(&BindlessDesc) -> Bindless  (Voll) one update-after-bind,
20//        partially bound set: binding 0 sampled textures, 1 samplers, 2 storage buffers,
21//        3 storage textures; table.group() binds it, table.layout() goes into pipeline layouts
22//   device.create_compute_pipeline / create_render_pipeline   (dynamic rendering: the target
23//        formats are part of the pipeline; viewport and scissor are dynamic); per stage: the
24//        entry point must exist with the stage's model, its pipeline constants become a
25//        VkSpecializationInfo (types and ids checked against the module), a compute workgroup
26//        size (LocalSize, LocalSizeId or the WorkgroupSize built-in, after specialization)
27//        must fit the limits, LocalSizeId needs maintenance4
28//   device.create_timestamps(n) -> Timestamps          read_ticks(first, n) / read_ms(first, n)
29//        (Err(NotReady) until every one was written and finished)
30//
31// Every object holds the device (Arc) and is destroyed through the device's garbage once the
32// GPU has finished the submissions made before it was dropped (vk_dev.rs).
33
34use alloc::sync::Arc;
35use alloc::vec;
36use alloc::vec::Vec;
37use core::ffi::c_void;
38
39use rt::sync::SpinLock;
40
41use crate::vk_dev::{Device, Garbage, Shared};
42use crate::vk_ffi as vk;
43use crate::vk_mem::Alloc;
44use crate::*;
45
46// ---------------------------------------------------------------- formats
47
48pub(crate) fn vk_format(f: Format) -> u32 {
49    use Format::*;
50    match f {
51        R8Unorm => vk::FORMAT_R8_UNORM,
52        Rg8Unorm => vk::FORMAT_R8G8_UNORM,
53        Rgba8Unorm => vk::FORMAT_R8G8B8A8_UNORM,
54        Rgba8Srgb => vk::FORMAT_R8G8B8A8_SRGB,
55        Bgra8Unorm => vk::FORMAT_B8G8R8A8_UNORM,
56        Bgra8Srgb => vk::FORMAT_B8G8R8A8_SRGB,
57        Rgb10a2Unorm => vk::FORMAT_A2B10G10R10_UNORM_PACK32,
58        Rg11b10Float => vk::FORMAT_B10G11R11_UFLOAT_PACK32,
59        R16Float => vk::FORMAT_R16_SFLOAT,
60        Rg16Float => vk::FORMAT_R16G16_SFLOAT,
61        Rgba16Float => vk::FORMAT_R16G16B16A16_SFLOAT,
62        R32Uint => vk::FORMAT_R32_UINT,
63        R32Sint => vk::FORMAT_R32_SINT,
64        R32Float => vk::FORMAT_R32_SFLOAT,
65        Rg32Uint => vk::FORMAT_R32G32_UINT,
66        Rg32Float => vk::FORMAT_R32G32_SFLOAT,
67        Rgba32Uint => vk::FORMAT_R32G32B32A32_UINT,
68        Rgba32Float => vk::FORMAT_R32G32B32A32_SFLOAT,
69        Depth16 => vk::FORMAT_D16_UNORM,
70        Depth32Float => vk::FORMAT_D32_SFLOAT,
71        Depth24Stencil8 => vk::FORMAT_D24_UNORM_S8_UINT,
72        Depth32FloatStencil8 => vk::FORMAT_D32_SFLOAT_S8_UINT,
73        Bc1Unorm => vk::FORMAT_BC1_RGBA_UNORM_BLOCK,
74        Bc1Srgb => vk::FORMAT_BC1_RGBA_SRGB_BLOCK,
75        Bc3Unorm => vk::FORMAT_BC3_UNORM_BLOCK,
76        Bc3Srgb => vk::FORMAT_BC3_SRGB_BLOCK,
77        Bc4Unorm => vk::FORMAT_BC4_UNORM_BLOCK,
78        Bc5Unorm => vk::FORMAT_BC5_UNORM_BLOCK,
79        Bc7Unorm => vk::FORMAT_BC7_UNORM_BLOCK,
80        Bc7Srgb => vk::FORMAT_BC7_SRGB_BLOCK,
81    }
82}
83
84/// every aspect of a format (barriers) and the one of copies and views
85fn aspects(f: Format) -> (u32, u32) {
86    if f.has_stencil() {
87        (vk::IMAGE_ASPECT_DEPTH_BIT | vk::IMAGE_ASPECT_STENCIL_BIT, vk::IMAGE_ASPECT_DEPTH_BIT)
88    } else if f.is_depth() {
89        (vk::IMAGE_ASPECT_DEPTH_BIT, vk::IMAGE_ASPECT_DEPTH_BIT)
90    } else {
91        (vk::IMAGE_ASPECT_COLOR_BIT, vk::IMAGE_ASPECT_COLOR_BIT)
92    }
93}
94
95pub(crate) fn compare_op(c: Compare) -> u32 {
96    match c {
97        Compare::Never => vk::COMPARE_OP_NEVER,
98        Compare::Less => vk::COMPARE_OP_LESS,
99        Compare::Equal => vk::COMPARE_OP_EQUAL,
100        Compare::LessEqual => vk::COMPARE_OP_LESS_OR_EQUAL,
101        Compare::Greater => vk::COMPARE_OP_GREATER,
102        Compare::NotEqual => vk::COMPARE_OP_NOT_EQUAL,
103        Compare::GreaterEqual => vk::COMPARE_OP_GREATER_OR_EQUAL,
104        Compare::Always => vk::COMPARE_OP_ALWAYS,
105    }
106}
107
108fn stage_bits(s: Stages) -> u32 {
109    let mut v = 0;
110    if s.intersects(Stages::VERTEX) {
111        v |= vk::SHADER_STAGE_VERTEX_BIT;
112    }
113    if s.intersects(Stages::FRAGMENT) {
114        v |= vk::SHADER_STAGE_FRAGMENT_BIT;
115    }
116    if s.intersects(Stages::COMPUTE) {
117        v |= vk::SHADER_STAGE_COMPUTE_BIT;
118    }
119    v
120}
121
122fn descriptor_type(k: BindingKind) -> u32 {
123    match k {
124        BindingKind::UniformBuffer => vk::DESCRIPTOR_TYPE_UNIFORM_BUFFER,
125        BindingKind::StorageBuffer | BindingKind::StorageBufferRead => vk::DESCRIPTOR_TYPE_STORAGE_BUFFER,
126        BindingKind::Texture => vk::DESCRIPTOR_TYPE_SAMPLED_IMAGE,
127        BindingKind::StorageTexture => vk::DESCRIPTOR_TYPE_STORAGE_IMAGE,
128        BindingKind::Sampler => vk::DESCRIPTOR_TYPE_SAMPLER,
129    }
130}
131
132fn nul_name(s: &str) -> Vec<u8> {
133    let mut v: Vec<u8> = s.bytes().filter(|&b| b != 0).collect();
134    v.push(0);
135    v
136}
137
138// ---------------------------------------------------------------- buffers
139
140pub(crate) struct BufInner {
141    sh: Arc<Shared>,
142    h: vk::Handle,
143    a: Alloc,
144    size: u64,
145    usage: BufferUsage,
146    mem: MemoryKind,
147}
148
149impl Drop for BufInner {
150    fn drop(&mut self) {
151        self.sh.defer(Garbage::Buffer(self.h, self.a));
152    }
153}
154
155/// GPU memory for data: vertices, indices, storage, uniforms, indirect arguments
156#[derive(Clone)]
157pub struct Buffer(Arc<BufInner>);
158
159impl Buffer {
160    pub fn size(&self) -> u64 {
161        self.0.size
162    }
163    pub fn usage(&self) -> BufferUsage {
164        self.0.usage
165    }
166    pub fn memory(&self) -> MemoryKind {
167        self.0.mem
168    }
169    pub(crate) fn handle(&self) -> vk::Handle {
170        self.0.h
171    }
172
173    fn mapped_range(&self, offset: u64, n: u64) -> Result<usize> {
174        let b = &self.0;
175        // by its kind, not by where it lies: on an integrated or CPU adapter all memory may be
176        // host-visible, and a program that wrote there would fail on the next GPU
177        if b.mem == MemoryKind::Device || b.a.ptr == 0 {
178            return Err(GpuError::Invalid("buffer not host-visible (MemoryKind::Device)"));
179        }
180        if offset.checked_add(n).is_none_or(|e| e > b.size) {
181            return Err(GpuError::Invalid("range outside the buffer"));
182        }
183        Ok(b.a.ptr + offset as usize)
184    }
185
186    /// the memory range of [offset, offset + n) widened to the non-coherent atom
187    fn atom_range(&self, offset: u64, n: u64) -> vk::MappedMemoryRange {
188        let b = &self.0;
189        let atom = b.sh.raw.non_coherent_atom_size.max(1);
190        let start = (b.a.offset + offset) / atom * atom;
191        let end = (b.a.offset + offset + n).div_ceil(atom) * atom;
192        let block_end = b.a.offset + b.a.size; // the allocation ends inside its block
193        vk::MappedMemoryRange {
194            s_type: vk::STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
195            memory: b.a.mem,
196            offset: start,
197            size: if end > block_end { vk::WHOLE_SIZE } else { end - start },
198            ..vk::Zero::zero()
199        }
200    }
201
202    /// copy data into a mapped buffer (Upload / Readback) at offset; the GPU sees it in the
203    /// next submission -- do not write what a pending submission still reads
204    pub fn write<T: Pod>(&self, offset: u64, data: &[T]) -> Result<()> {
205        let bytes = bytes_of(data);
206        let p = self.mapped_range(offset, bytes.len() as u64)?;
207        // SAFETY: p addresses bytes.len() mapped bytes inside the buffer's allocation.
208        unsafe { core::ptr::copy_nonoverlapping(bytes.as_ptr(), p as *mut u8, bytes.len()) };
209        if !self.0.a.coherent && !bytes.is_empty() {
210            let r = self.atom_range(offset, bytes.len() as u64);
211            // SAFETY: a range of the mapped memory of this allocation.
212            self.0.sh.vk(unsafe { (self.0.sh.f.flush_mapped_memory_ranges)(self.0.sh.dev, 1, &r) })?;
213        }
214        Ok(())
215    }
216
217    /// copy a mapped buffer's contents at offset into out (wait for the submission that wrote
218    /// them first)
219    pub fn read<T: Pod>(&self, offset: u64, out: &mut [T]) -> Result<()> {
220        let n = core::mem::size_of_val(out);
221        let p = self.mapped_range(offset, n as u64)?;
222        if !self.0.a.coherent && n > 0 {
223            let r = self.atom_range(offset, n as u64);
224            // SAFETY: a range of the mapped memory of this allocation.
225            self.0.sh.vk(unsafe { (self.0.sh.f.invalidate_mapped_memory_ranges)(self.0.sh.dev, 1, &r) })?;
226        }
227        // SAFETY: p addresses n mapped bytes; T is Pod, so any bytes are a valid T.
228        unsafe { core::ptr::copy_nonoverlapping(p as *const u8, out.as_mut_ptr() as *mut u8, n) };
229        Ok(())
230    }
231}
232
233impl Device {
234    pub fn create_buffer(&self, desc: &BufferDesc) -> Result<Buffer> {
235        let sh = &self.sh;
236        sh.check()?;
237        if desc.size == 0 {
238            return Err(GpuError::Invalid("buffer size 0"));
239        }
240        let u = desc.usage;
241        let mut usage = vk::BUFFER_USAGE_TRANSFER_SRC_BIT | vk::BUFFER_USAGE_TRANSFER_DST_BIT;
242        for (b, v) in [
243            (BufferUsage::UNIFORM, vk::BUFFER_USAGE_UNIFORM_BUFFER_BIT),
244            (BufferUsage::STORAGE, vk::BUFFER_USAGE_STORAGE_BUFFER_BIT),
245            (BufferUsage::INDEX, vk::BUFFER_USAGE_INDEX_BUFFER_BIT),
246            (BufferUsage::VERTEX, vk::BUFFER_USAGE_VERTEX_BUFFER_BIT),
247            (BufferUsage::INDIRECT, vk::BUFFER_USAGE_INDIRECT_BUFFER_BIT),
248        ] {
249            if u.contains(b) {
250                usage |= v;
251            }
252        }
253        let mut st = sh.st.lock();
254        let (h, a) = sh.raw_buffer(&mut st, desc.size, usage, desc.memory)?;
255        drop(st);
256        Ok(Buffer(Arc::new(BufInner { sh: sh.clone(), h, a, size: desc.size, usage: u, mem: desc.memory })))
257    }
258}
259
260// ---------------------------------------------------------------- textures
261
262pub(crate) struct TexInner {
263    sh: Arc<Shared>,
264    pub image: vk::Handle,
265    a: Option<Alloc>,
266    owned: bool,
267    pub desc: TextureDesc,
268    pub aspect_all: u32,
269    pub aspect_copy: u32,
270}
271
272impl Drop for TexInner {
273    fn drop(&mut self) {
274        if self.owned {
275            self.sh.defer(Garbage::Image(self.image, self.a));
276        }
277    }
278}
279
280pub(crate) struct ViewInner {
281    sh: Arc<Shared>,
282    pub h: vk::Handle,
283    pub tex: Arc<TexInner>,
284    pub base_mip: u32,
285    pub mips: u32,
286    pub base_layer: u32,
287    pub layers: u32,
288}
289
290impl Drop for ViewInner {
291    fn drop(&mut self) {
292        self.sh.defer(Garbage::View(self.h));
293    }
294}
295
296/// a range of a texture's mips and layers as shaders and render passes see it
297#[derive(Clone)]
298pub struct TextureView(pub(crate) Arc<ViewInner>);
299
300impl TextureView {
301    pub fn format(&self) -> Format {
302        self.0.tex.desc.format
303    }
304    /// width and height of the view's first mip
305    pub fn size(&self) -> (u32, u32) {
306        let d = &self.0.tex.desc;
307        ((d.width >> self.0.base_mip).max(1), (d.height >> self.0.base_mip).max(1))
308    }
309    pub(crate) fn usage(&self) -> TextureUsage {
310        self.0.tex.desc.usage
311    }
312}
313
314/// an image on the GPU: 2D, 2D array, cube (array) or 3D, with mips
315#[derive(Clone)]
316pub struct Texture {
317    pub(crate) t: Arc<TexInner>,
318    view: TextureView,
319}
320
321impl Texture {
322    /// the view of everything (its natural dimension)
323    pub fn view(&self) -> &TextureView {
324        &self.view
325    }
326    pub fn width(&self) -> u32 {
327        self.t.desc.width
328    }
329    pub fn height(&self) -> u32 {
330        self.t.desc.height
331    }
332    pub fn depth(&self) -> u32 {
333        self.t.desc.depth
334    }
335    pub fn layers(&self) -> u32 {
336        self.t.desc.layers
337    }
338    pub fn mips(&self) -> u32 {
339        self.t.desc.mips
340    }
341    pub fn format(&self) -> Format {
342        self.t.desc.format
343    }
344    pub fn usage(&self) -> TextureUsage {
345        self.t.desc.usage
346    }
347    pub(crate) fn image(&self) -> vk::Handle {
348        self.t.image
349    }
350
351    /// a view of some mips / layers (counts 0: to the end)
352    pub fn create_view(&self, desc: &ViewDesc) -> Result<TextureView> {
353        let d = &self.t.desc;
354        let mips = if desc.mips == 0 { d.mips.saturating_sub(desc.base_mip) } else { desc.mips };
355        let layers = if desc.layers == 0 { d.layers.saturating_sub(desc.base_layer) } else { desc.layers };
356        if mips == 0 || desc.base_mip + mips > d.mips || layers == 0 || desc.base_layer + layers > d.layers {
357            return Err(GpuError::Invalid("view range outside the texture"));
358        }
359        let ok = match desc.dim {
360            ViewDim::D2 => d.depth == 1 && layers == 1,
361            ViewDim::D2Array => d.depth == 1,
362            ViewDim::Cube => d.cube && layers == 6,
363            ViewDim::CubeArray => d.cube && layers % 6 == 0,
364            ViewDim::D3 => d.depth > 1,
365        };
366        if !ok {
367            return Err(GpuError::Invalid("view dimension does not fit the texture"));
368        }
369        make_view(&self.t, desc.dim, desc.base_mip, mips, desc.base_layer, layers)
370    }
371}
372
373fn make_view(t: &Arc<TexInner>, dim: ViewDim, base_mip: u32, mips: u32, base_layer: u32, layers: u32) -> Result<TextureView> {
374    let sh = &t.sh;
375    let vi = vk::ImageViewCreateInfo {
376        s_type: vk::STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
377        image: t.image,
378        view_type: match dim {
379            ViewDim::D2 => vk::IMAGE_VIEW_TYPE_2D,
380            ViewDim::D2Array => vk::IMAGE_VIEW_TYPE_2D_ARRAY,
381            ViewDim::Cube => vk::IMAGE_VIEW_TYPE_CUBE,
382            ViewDim::CubeArray => vk::IMAGE_VIEW_TYPE_CUBE_ARRAY,
383            ViewDim::D3 => vk::IMAGE_VIEW_TYPE_3D,
384        },
385        format: vk_format(t.desc.format),
386        subresource_range: vk::ImageSubresourceRange {
387            aspect_mask: t.aspect_copy,
388            base_mip_level: base_mip,
389            level_count: mips,
390            base_array_layer: base_layer,
391            layer_count: layers,
392        },
393        ..vk::Zero::zero()
394    };
395    let mut h = 0;
396    // SAFETY: a valid create info for an image of this device.
397    sh.vk(unsafe { (sh.f.create_image_view)(sh.dev, &vi, core::ptr::null(), &mut h) })?;
398    Ok(TextureView(Arc::new(ViewInner { sh: sh.clone(), h, tex: t.clone(), base_mip, mips, base_layer, layers })))
399}
400
401/// the natural view dimension of a texture
402fn default_dim(d: &TextureDesc) -> ViewDim {
403    if d.depth > 1 {
404        ViewDim::D3
405    } else if d.cube {
406        if d.layers == 6 { ViewDim::Cube } else { ViewDim::CubeArray }
407    } else if d.layers > 1 {
408        ViewDim::D2Array
409    } else {
410        ViewDim::D2
411    }
412}
413
414/// a swapchain image as a Texture (not owned: the swapchain destroys it)
415pub(crate) fn wrap_image(sh: &Arc<Shared>, image: vk::Handle, desc: TextureDesc) -> Result<Texture> {
416    let (aspect_all, aspect_copy) = aspects(desc.format);
417    let t = Arc::new(TexInner { sh: sh.clone(), image, a: None, owned: false, desc, aspect_all, aspect_copy });
418    let view = make_view(&t, ViewDim::D2, 0, 1, 0, 1)?;
419    Ok(Texture { t, view })
420}
421
422impl Device {
423    pub fn create_texture(&self, desc: &TextureDesc) -> Result<Texture> {
424        let sh = &self.sh;
425        sh.check()?;
426        let d = *desc;
427        let lim = &sh.limits;
428        let max_dim = if d.depth > 1 { lim.max_texture_3d } else { lim.max_texture_2d };
429        if d.width == 0 || d.height == 0 || d.depth == 0 || d.layers == 0 || d.mips == 0 {
430            return Err(GpuError::Invalid("texture size, layers or mips 0"));
431        }
432        if d.width > max_dim || d.height > max_dim || d.depth > max_dim || d.layers > lim.max_texture_layers {
433            return Err(GpuError::Invalid("texture larger than the device's limits"));
434        }
435        let longest = d.width.max(d.height).max(d.depth);
436        if d.mips > 32 - longest.leading_zeros() {
437            return Err(GpuError::Invalid("more mips than the size has"));
438        }
439        if d.depth > 1 && (d.layers > 1 || d.cube || d.format.is_depth()) {
440            return Err(GpuError::Invalid("3D textures have one layer and a colour format"));
441        }
442        if d.cube && (d.layers % 6 != 0 || d.width != d.height) {
443            return Err(GpuError::Invalid("cube textures are square with 6 * n layers"));
444        }
445        if d.usage == TextureUsage::NONE {
446            return Err(GpuError::Invalid("texture without usage"));
447        }
448        if d.format.is_compressed() && !sh.features.texture_bc {
449            return Err(GpuError::Unsupported("BC compressed textures"));
450        }
451        let fmt = vk_format(d.format);
452        let mut fp: vk::FormatProperties = vk::Zero::zero();
453        // SAFETY: plain out-parameter.
454        unsafe { (sh.inst.f.get_physical_device_format_properties)(sh.pd, fmt, &mut fp) };
455        let of = fp.optimal_tiling_features;
456        let mut usage = 0;
457        for (u, v, need) in [
458            (TextureUsage::COPY_SRC, vk::IMAGE_USAGE_TRANSFER_SRC_BIT, 0),
459            (TextureUsage::COPY_DST, vk::IMAGE_USAGE_TRANSFER_DST_BIT, 0),
460            (TextureUsage::SAMPLED, vk::IMAGE_USAGE_SAMPLED_BIT, vk::FORMAT_FEATURE_SAMPLED_IMAGE_BIT),
461            (TextureUsage::STORAGE, vk::IMAGE_USAGE_STORAGE_BIT, vk::FORMAT_FEATURE_STORAGE_IMAGE_BIT),
462            (TextureUsage::COLOR_TARGET, vk::IMAGE_USAGE_COLOR_ATTACHMENT_BIT, vk::FORMAT_FEATURE_COLOR_ATTACHMENT_BIT),
463            (TextureUsage::DEPTH_TARGET, vk::IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
464             vk::FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT),
465        ] {
466            if d.usage.contains(u) {
467                if of & need != need {
468                    return Err(GpuError::Unsupported("format with this usage"));
469                }
470                usage |= v;
471            }
472        }
473        if d.usage.contains(TextureUsage::COLOR_TARGET) && d.format.is_depth()
474            || d.usage.contains(TextureUsage::DEPTH_TARGET) && !d.format.is_depth()
475        {
476            return Err(GpuError::Invalid("colour targets need colour formats, depth targets depth formats"));
477        }
478        let ii = vk::ImageCreateInfo {
479            s_type: vk::STRUCTURE_TYPE_IMAGE_CREATE_INFO,
480            flags: if d.cube { vk::IMAGE_CREATE_CUBE_COMPATIBLE_BIT } else { 0 },
481            image_type: if d.depth > 1 { vk::IMAGE_TYPE_3D } else { vk::IMAGE_TYPE_2D },
482            format: fmt,
483            extent: vk::Extent3D { width: d.width, height: d.height, depth: d.depth },
484            mip_levels: d.mips,
485            array_layers: d.layers,
486            samples: vk::SAMPLE_COUNT_1_BIT,
487            tiling: vk::IMAGE_TILING_OPTIMAL,
488            usage,
489            sharing_mode: vk::SHARING_MODE_EXCLUSIVE,
490            initial_layout: vk::IMAGE_LAYOUT_UNDEFINED,
491            ..vk::Zero::zero()
492        };
493        let mut image = 0;
494        // SAFETY: a valid create info (checked above).
495        sh.vk(unsafe { (sh.f.create_image)(sh.dev, &ii, core::ptr::null(), &mut image) })?;
496        let mut req: vk::MemoryRequirements = vk::Zero::zero();
497        // SAFETY: the image just created.
498        unsafe { (sh.f.get_image_memory_requirements)(sh.dev, image, &mut req) };
499        let mut st = sh.st.lock();
500        let a = match st.mem.alloc(&sh.f, sh.dev, &req, MemoryKind::Device, false) {
501            Ok(a) => a,
502            Err(e) => {
503                // SAFETY: an unbound, unused image.
504                unsafe { (sh.f.destroy_image)(sh.dev, image, core::ptr::null()) };
505                return Err(e);
506            }
507        };
508        drop(st);
509        // SAFETY: the allocation satisfies the image's requirements.
510        if let Err(e) = sh.vk(unsafe { (sh.f.bind_image_memory)(sh.dev, image, a.mem, a.offset) }) {
511            sh.defer(Garbage::Image(image, Some(a)));
512            return Err(e);
513        }
514        let (aspect_all, aspect_copy) = aspects(d.format);
515        let t = Arc::new(TexInner { sh: sh.clone(), image, a: Some(a), owned: true, desc: d, aspect_all, aspect_copy });
516        let view = make_view(&t, default_dim(&d), 0, d.mips, 0, d.layers)?;
517        Ok(Texture { t, view })
518    }
519}
520
521/// the subresource range of mips / layers of a texture (every aspect)
522pub(crate) fn subresources(t: &Texture, base_mip: u32, mips: u32, base_layer: u32, layers: u32) -> vk::ImageSubresourceRange {
523    vk::ImageSubresourceRange {
524        aspect_mask: t.t.aspect_all,
525        base_mip_level: base_mip,
526        level_count: mips,
527        base_array_layer: base_layer,
528        layer_count: layers,
529    }
530}
531
532/// a buffer <-> texture copy: the region checked against the texture, the data layout against
533/// the format -> (the copy with buffer_offset = layout.offset, bytes from layout.offset on);
534/// data_len: the bytes behind the layout (buffer size or slice length)
535pub(crate) fn copy_region(r: &TextureRegion, l: &DataLayout, data_len: u64) -> Result<(vk::BufferImageCopy, u64)> {
536    let t = r.texture;
537    let d = &t.t.desc;
538    if r.mip >= d.mips || r.layers == 0 || r.base_layer + r.layers > d.layers {
539        return Err(GpuError::Invalid("copy: mip or layers outside the texture"));
540    }
541    let (mw, mh, md) = ((d.width >> r.mip).max(1), (d.height >> r.mip).max(1), (d.depth >> r.mip).max(1));
542    let [x, y, z] = r.origin;
543    let [w, h, dd] = r.size;
544    if w == 0 || h == 0 || dd == 0 || x + w > mw || y + h > mh || z + dd > md {
545        return Err(GpuError::Invalid("copy: region outside the mip"));
546    }
547    let f = d.format;
548    let (bw, bb) = (f.block_dim(), f.block_bytes());
549    if bw > 1 && (x % bw != 0 || y % bw != 0 || (w % bw != 0 && x + w != mw) || (h % bw != 0 && y + h != mh)) {
550        return Err(GpuError::Invalid("copy: compressed regions are whole blocks"));
551    }
552    if f.has_stencil() {
553        return Err(GpuError::Unsupported("copies of depth-stencil formats"));
554    }
555    let bx = w.div_ceil(bw) as u64;
556    let by = h.div_ceil(bw) as u64;
557    let row = bx * bb as u64;
558    let bpr = if l.bytes_per_row == 0 { row } else { l.bytes_per_row as u64 };
559    let rows = if l.rows_per_image == 0 { by } else { l.rows_per_image as u64 };
560    if bpr < row || bpr % bb as u64 != 0 || rows < by {
561        return Err(GpuError::Invalid("copy: bytes_per_row / rows_per_image too small or not whole texels"));
562    }
563    let align = if f.is_depth() { 4 } else { bb as u64 };
564    if l.offset % align != 0 {
565        return Err(GpuError::Invalid("copy: data offset not a multiple of the texel size"));
566    }
567    let slices = (dd * r.layers) as u64;
568    let span = bpr * rows * (slices - 1) + bpr * (by - 1) + row;
569    if l.offset.checked_add(span).is_none_or(|e| e > data_len) {
570        return Err(GpuError::Invalid("copy: data smaller than the region"));
571    }
572    let copy = vk::BufferImageCopy {
573        buffer_offset: l.offset,
574        buffer_row_length: (bpr / bb as u64 * bw as u64) as u32,
575        buffer_image_height: (rows * bw as u64) as u32,
576        image_subresource: vk::ImageSubresourceLayers {
577            aspect_mask: t.t.aspect_copy,
578            mip_level: r.mip,
579            base_array_layer: r.base_layer,
580            layer_count: r.layers,
581        },
582        image_offset: vk::Offset3D { x: x as i32, y: y as i32, z: z as i32 },
583        image_extent: vk::Extent3D { width: w, height: h, depth: dd },
584    };
585    Ok((copy, span))
586}
587
588// ---------------------------------------------------------------- samplers, shaders
589
590pub(crate) struct SamplerInner {
591    sh: Arc<Shared>,
592    pub h: vk::Handle,
593}
594
595impl Drop for SamplerInner {
596    fn drop(&mut self) {
597        self.sh.defer(Garbage::Sampler(self.h));
598    }
599}
600
601/// how textures are filtered and addressed
602#[derive(Clone)]
603pub struct Sampler(pub(crate) Arc<SamplerInner>);
604
605impl Device {
606    pub fn create_sampler(&self, desc: &SamplerDesc) -> Result<Sampler> {
607        let sh = &self.sh;
608        sh.check()?;
609        let filter = |f: Filter| if f == Filter::Linear { vk::FILTER_LINEAR } else { vk::FILTER_NEAREST };
610        let addr = |a: AddressMode| match a {
611            AddressMode::Repeat => vk::SAMPLER_ADDRESS_MODE_REPEAT,
612            AddressMode::MirrorRepeat => vk::SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT,
613            AddressMode::ClampToEdge => vk::SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
614        };
615        let aniso = desc.anisotropy > 1;
616        if aniso && !sh.features.anisotropy {
617            return Err(GpuError::Unsupported("anisotropic filtering"));
618        }
619        if desc.lod_min < 0.0 || desc.lod_max < desc.lod_min {
620            return Err(GpuError::Invalid("sampler lod range"));
621        }
622        let si = vk::SamplerCreateInfo {
623            s_type: vk::STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
624            mag_filter: filter(desc.mag),
625            min_filter: filter(desc.min),
626            mipmap_mode: if desc.mip == Filter::Linear { vk::SAMPLER_MIPMAP_MODE_LINEAR } else { vk::SAMPLER_MIPMAP_MODE_NEAREST },
627            address_mode_u: addr(desc.address[0]),
628            address_mode_v: addr(desc.address[1]),
629            address_mode_w: addr(desc.address[2]),
630            anisotropy_enable: aniso as u32,
631            max_anisotropy: (desc.anisotropy as f32).min(sh.limits.max_anisotropy).max(1.0),
632            compare_enable: desc.compare.is_some() as u32,
633            compare_op: desc.compare.map_or(vk::COMPARE_OP_NEVER, compare_op),
634            min_lod: desc.lod_min,
635            max_lod: desc.lod_max,
636            border_color: vk::BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
637            ..vk::Zero::zero()
638        };
639        let mut h = 0;
640        // SAFETY: valid create info.
641        sh.vk(unsafe { (sh.f.create_sampler)(sh.dev, &si, core::ptr::null(), &mut h) })?;
642        Ok(Sampler(Arc::new(SamplerInner { sh: sh.clone(), h })))
643    }
644}
645
646pub(crate) struct ShaderInner {
647    sh: Arc<Shared>,
648    pub h: vk::Handle,
649    /// the words (for the workgroup size once the pipeline constants are known)
650    words: Vec<u32>,
651    entries: Vec<shader::reflect::Entry>,
652    spec: Vec<shader::reflect::SpecConst>,
653    local_size_id: bool,
654}
655
656impl Drop for ShaderInner {
657    fn drop(&mut self) {
658        self.sh.defer(Garbage::Shader(self.h));
659    }
660}
661
662/// compiled shader code (SPIR-V)
663#[derive(Clone)]
664pub struct ShaderModule(pub(crate) Arc<ShaderInner>);
665
666impl ShaderModule {
667    /// a module from SPIR-V words: version up to 1.3 on Vulkan 1.1 devices, 1.5 on 1.2, 1.6 on
668    /// 1.3 (the magic number and version are checked; the code itself is the driver's)
669    pub fn from_spirv(device: &Device, code: &[u32]) -> Result<ShaderModule> {
670        let sh = &device.sh;
671        sh.check()?;
672        if code.len() < 5 || code[0] != 0x0723_0203 {
673            return Err(GpuError::Invalid("not SPIR-V (magic number)"));
674        }
675        let (maj, min) = (sh.info.api_version.0, sh.info.api_version.1);
676        let max = match (maj, min) {
677            (1, 1) => 0x0001_0300,
678            (1, 2) => 0x0001_0500,
679            _ => 0x0001_0600,
680        };
681        if code[1] > max || code[1] < 0x0001_0000 {
682            return Err(GpuError::Unsupported("this SPIR-V version"));
683        }
684        let entries = shader::reflect::entry_points(code).map_err(|_| GpuError::Invalid("SPIR-V instructions do not fit the words"))?;
685        let spec = shader::reflect::spec_constants(code).map_err(|_| GpuError::Invalid("SpecId on something else than a scalar specialization constant"))?;
686        if spec.iter().enumerate().any(|(i, a)| spec[..i].iter().any(|b| b.spec_id == a.spec_id)) {
687            return Err(GpuError::Invalid("a SpecId twice in the module"));
688        }
689        let ci = vk::ShaderModuleCreateInfo {
690            s_type: vk::STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
691            code_size: code.len() * 4,
692            p_code: code.as_ptr(),
693            ..vk::Zero::zero()
694        };
695        let mut h = 0;
696        // SAFETY: code is a SPIR-V word array of code_size bytes.
697        sh.vk(unsafe { (sh.f.create_shader_module)(sh.dev, &ci, core::ptr::null(), &mut h) })?;
698        let local_size_id = shader::reflect::uses_local_size_id(code);
699        Ok(ShaderModule(Arc::new(ShaderInner { sh: sh.clone(), h, words: code.to_vec(), entries, spec, local_size_id })))
700    }
701
702    /// the entry points: (model as SPIR-V numbers it: 0 vertex, 4 fragment, 5 compute, name)
703    pub fn entry_points(&self) -> Vec<(u32, &str)> {
704        self.0.entries.iter().map(|e| (e.model, e.name.as_str())).collect()
705    }
706
707    /// the pipeline-overridable constants the module declares: (id, kind, default value)
708    pub fn constants(&self) -> Vec<(u32, shader::reflect::Kind, f64)> {
709        self.0.spec.iter().map(|c| (c.spec_id, c.kind, kind_value(c.kind, c.default))).collect()
710    }
711}
712
713/// the bits of a constant of kind k as f64
714fn kind_value(k: shader::reflect::Kind, v: u64) -> f64 {
715    use shader::reflect::Kind;
716    match k {
717        Kind::Bool => (v != 0) as u32 as f64,
718        Kind::Int { width, signed: true } => {
719            let s = 64 - width.min(64);
720            (((v << s) as i64) >> s) as f64
721        }
722        Kind::Int { .. } => v as f64,
723        Kind::Float { width: 16 } => shader::reflect::f16_to_f32(v as u16) as f64,
724        Kind::Float { width: 32 } => f32::from_bits(v as u32) as f64,
725        Kind::Float { .. } => f64::from_bits(v),
726    }
727}
728
729/// a pipeline constant's value as the bits of kind k (WebGPU's conversions, lib.rs Constant)
730fn constant_bits(k: shader::reflect::Kind, x: f64) -> Result<u64> {
731    use shader::reflect::Kind;
732    let bad = GpuError::Invalid("pipeline constant: the value does not fit the override's type");
733    match k {
734        Kind::Bool => Ok((x != 0.0 && !x.is_nan()) as u64),
735        Kind::Int { width, signed } => {
736            if !x.is_finite() {
737                return Err(bad);
738            }
739            let t = x as i128; // toward 0 (saturating: out of range anyway)
740            let w = width.clamp(1, 64);
741            let (lo, hi) = if signed { (-(1i128 << (w - 1)), (1i128 << (w - 1)) - 1) } else { (0, (1i128 << w) - 1) };
742            if t < lo || t > hi {
743                return Err(bad);
744            }
745            let m = if w >= 64 { u64::MAX } else { (1u64 << w) - 1 };
746            Ok(t as u64 & m)
747        }
748        Kind::Float { width } => {
749            if !x.is_finite() {
750                return Err(bad);
751            }
752            match width {
753                16 => {
754                    let h = shader::reflect::f16_from_f32(x as f32);
755                    if h & 0x7c00 == 0x7c00 || !(x as f32).is_finite() {
756                        return Err(bad);
757                    }
758                    Ok(h as u64)
759                }
760                32 => {
761                    let f = x as f32;
762                    if !f.is_finite() {
763                        return Err(bad);
764                    }
765                    Ok(f.to_bits() as u64)
766                }
767                _ => Ok(x.to_bits()),
768            }
769        }
770    }
771}
772
773/// a stage's pipeline constants checked against its module -> (map entries, data,
774/// (SpecId, bits) for shader::reflect)
775fn specialize(m: &ShaderInner, constants: &[Constant]) -> Result<(Vec<vk::SpecializationMapEntry>, Vec<u8>, Vec<(u32, u64)>)> {
776    let mut map = Vec::with_capacity(constants.len());
777    let mut data = Vec::new();
778    let mut set = Vec::with_capacity(constants.len());
779    for (i, c) in constants.iter().enumerate() {
780        if constants[..i].iter().any(|d| d.id == c.id) {
781            return Err(GpuError::Invalid("pipeline constant: an id twice"));
782        }
783        let Some(decl) = m.spec.iter().find(|s| s.spec_id == c.id) else {
784            return Err(GpuError::Invalid("pipeline constant: the module has no override with this id"));
785        };
786        let bits = constant_bits(decl.kind, c.value)?;
787        let size = match decl.kind {
788            shader::reflect::Kind::Bool => 4, // VkBool32
789            shader::reflect::Kind::Int { width, .. } | shader::reflect::Kind::Float { width } => (width as usize).div_ceil(8),
790        };
791        map.push(vk::SpecializationMapEntry { constant_id: c.id, offset: data.len() as u32, size });
792        data.extend_from_slice(&bits.to_le_bytes()[..size]);
793        while data.len() % 4 != 0 {
794            data.push(0);
795        }
796        set.push((c.id, bits));
797    }
798    Ok((map, data, set))
799}
800
801/// the checks of a stage (its entry point exists with model; the constants) -> what
802/// VkPipelineShaderStageCreateInfo needs
803fn stage_info(sh: &Shared, st: &ShaderStage, model: shader::Model) -> Result<(Vec<vk::SpecializationMapEntry>, Vec<u8>, Vec<(u32, u64)>)> {
804    let m = &st.module.0;
805    if !m.entries.iter().any(|e| e.name == st.entry && e.model == model as u32) {
806        return Err(GpuError::Invalid("the module has no entry point of this name for the stage"));
807    }
808    if model == shader::Model::GLCompute && m.local_size_id && !sh.maintenance4 {
809        return Err(GpuError::Unsupported("LocalSizeId (maintenance4, Vulkan 1.3)"));
810    }
811    specialize(m, st.constants)
812}
813
814// ---------------------------------------------------------------- bindings
815
816pub(crate) struct BglInner {
817    sh: Arc<Shared>,
818    pub h: vk::Handle,
819    pub bindings: Vec<Binding>, // sorted by binding (empty for a bindless layout)
820    needs: Vec<vk::DescriptorPoolSize>,
821}
822
823impl Drop for BglInner {
824    fn drop(&mut self) {
825        self.sh.defer(Garbage::SetLayout(self.h));
826    }
827}
828
829/// the bindings of one bind group (@group)
830#[derive(Clone)]
831pub struct BindGroupLayout(pub(crate) Arc<BglInner>);
832
833impl Device {
834    pub fn create_bind_group_layout(&self, bindings: &[Binding]) -> Result<BindGroupLayout> {
835        let sh = &self.sh;
836        sh.check()?;
837        let mut b: Vec<Binding> = bindings.to_vec();
838        b.sort_by_key(|x| x.binding);
839        if b.windows(2).any(|w| w[0].binding == w[1].binding) {
840            return Err(GpuError::Invalid("a binding number twice"));
841        }
842        for x in &b {
843            if stage_bits(x.stages) == 0 {
844                return Err(GpuError::Invalid("binding without stages"));
845            }
846            if x.kind == BindingKind::StorageBuffer && x.stages.intersects(Stages::VERTEX) && !sh.features.vertex_stores {
847                return Err(GpuError::Unsupported("writable storage buffers in vertex shaders"));
848            }
849        }
850        let vb: Vec<vk::DescriptorSetLayoutBinding> = b
851            .iter()
852            .map(|x| vk::DescriptorSetLayoutBinding {
853                binding: x.binding,
854                descriptor_type: descriptor_type(x.kind),
855                descriptor_count: 1,
856                stage_flags: stage_bits(x.stages),
857                p_immutable_samplers: core::ptr::null(),
858            })
859            .collect();
860        let ci = vk::DescriptorSetLayoutCreateInfo {
861            s_type: vk::STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
862            binding_count: vb.len() as u32,
863            p_bindings: vb.as_ptr(),
864            ..vk::Zero::zero()
865        };
866        let mut h = 0;
867        // SAFETY: valid create info.
868        sh.vk(unsafe { (sh.f.create_descriptor_set_layout)(sh.dev, &ci, core::ptr::null(), &mut h) })?;
869        let needs = vb.iter().map(|x| vk::DescriptorPoolSize { type_: x.descriptor_type, descriptor_count: 1 }).collect();
870        Ok(BindGroupLayout(Arc::new(BglInner { sh: sh.clone(), h, bindings: b, needs })))
871    }
872}
873
874pub(crate) struct PlInner {
875    sh: Arc<Shared>,
876    pub h: vk::Handle,
877    pub sets: Vec<vk::Handle>,
878    pub push: u32,
879    _groups: Vec<BindGroupLayout>,
880}
881
882impl Drop for PlInner {
883    fn drop(&mut self) {
884        self.sh.defer(Garbage::PipeLayout(self.h));
885    }
886}
887
888/// the bind group layouts and push constants of pipelines
889#[derive(Clone)]
890pub struct PipelineLayout(pub(crate) Arc<PlInner>);
891
892impl Device {
893    pub fn create_pipeline_layout(&self, desc: &PipelineLayoutDesc) -> Result<PipelineLayout> {
894        let sh = &self.sh;
895        sh.check()?;
896        if desc.groups.len() > BIND_GROUPS_MAX as usize || desc.groups.len() > sh.raw.max_bound_descriptor_sets as usize {
897            return Err(GpuError::Invalid("too many bind groups"));
898        }
899        if desc.push_constants % 4 != 0 || desc.push_constants > PUSH_CONSTANTS_MAX {
900            return Err(GpuError::Invalid("push constants: a multiple of 4, at most PUSH_CONSTANTS_MAX"));
901        }
902        let sets: Vec<vk::Handle> = desc.groups.iter().map(|g| g.0.h).collect();
903        let range = vk::PushConstantRange { stage_flags: vk::SHADER_STAGE_ALL, offset: 0, size: desc.push_constants };
904        let ci = vk::PipelineLayoutCreateInfo {
905            s_type: vk::STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
906            set_layout_count: sets.len() as u32,
907            p_set_layouts: sets.as_ptr(),
908            push_constant_range_count: (desc.push_constants > 0) as u32,
909            p_push_constant_ranges: &range,
910            ..vk::Zero::zero()
911        };
912        let mut h = 0;
913        // SAFETY: valid create info; the set layouts are alive.
914        sh.vk(unsafe { (sh.f.create_pipeline_layout)(sh.dev, &ci, core::ptr::null(), &mut h) })?;
915        Ok(PipelineLayout(Arc::new(PlInner {
916            sh: sh.clone(),
917            h,
918            sets,
919            push: desc.push_constants,
920            _groups: desc.groups.iter().map(|g| (*g).clone()).collect(),
921        })))
922    }
923}
924
925/// what a bind group keeps alive (held for their drop only)
926#[derive(Clone)]
927#[allow(dead_code)]
928enum Keep {
929    Buf(Buffer),
930    View(TextureView),
931    Sampler(Sampler),
932}
933
934pub(crate) struct BgInner {
935    sh: Arc<Shared>,
936    pub set: vk::Handle,
937    pool: vk::Handle,
938    own_pool: bool,
939    pub layout: vk::Handle,
940    _layout: BindGroupLayout,
941    _keep: Vec<Keep>,
942}
943
944impl Drop for BgInner {
945    fn drop(&mut self) {
946        if self.own_pool {
947            self.sh.defer(Garbage::Pool(self.pool));
948        } else {
949            self.sh.defer(Garbage::Set(self.pool, self.set));
950        }
951    }
952}
953
954/// resources bound for shaders (a descriptor set)
955#[derive(Clone)]
956pub struct BindGroup(pub(crate) Arc<BgInner>);
957
958impl Device {
959    pub fn create_bind_group(&self, desc: &BindGroupDesc) -> Result<BindGroup> {
960        let sh = &self.sh;
961        sh.check()?;
962        let lay = &desc.layout.0;
963        if desc.entries.len() != lay.bindings.len() {
964            return Err(GpuError::Invalid("bind group: one entry per binding of the layout"));
965        }
966        let mut keep = Vec::with_capacity(desc.entries.len());
967        let mut bufs: Vec<vk::DescriptorBufferInfo> = Vec::with_capacity(desc.entries.len());
968        let mut imgs: Vec<vk::DescriptorImageInfo> = Vec::with_capacity(desc.entries.len());
969        let mut which: Vec<(u32, u32, bool, usize)> = Vec::with_capacity(desc.entries.len()); // binding, type, buffer?, index
970        for e in desc.entries {
971            let Some(b) = lay.bindings.iter().find(|b| b.binding == e.binding) else {
972                return Err(GpuError::Invalid("bind group: entry for a binding the layout lacks"));
973            };
974            if which.iter().any(|w| w.0 == e.binding) {
975                return Err(GpuError::Invalid("bind group: a binding twice"));
976            }
977            let ty = descriptor_type(b.kind);
978            match (b.kind, e.resource) {
979                (BindingKind::UniformBuffer | BindingKind::StorageBuffer | BindingKind::StorageBufferRead,
980                 Resource::Buffer { buffer, offset, size }) => {
981                    let uniform = b.kind == BindingKind::UniformBuffer;
982                    let (need, align, max) = if uniform {
983                        (BufferUsage::UNIFORM, sh.limits.min_uniform_offset_alignment, sh.limits.max_uniform_binding)
984                    } else {
985                        (BufferUsage::STORAGE, sh.limits.min_storage_offset_alignment, sh.limits.max_storage_binding)
986                    };
987                    if !buffer.usage().contains(need) {
988                        return Err(GpuError::Invalid("bind group: buffer lacks UNIFORM / STORAGE usage"));
989                    }
990                    let range = if size == 0 { buffer.size().saturating_sub(offset) } else { size };
991                    if offset % align.max(1) as u64 != 0 || range == 0 || offset + range > buffer.size() || range > max as u64 {
992                        return Err(GpuError::Invalid("bind group: buffer range (alignment, size, limit)"));
993                    }
994                    which.push((e.binding, ty, true, bufs.len()));
995                    bufs.push(vk::DescriptorBufferInfo { buffer: buffer.handle(), offset, range });
996                    keep.push(Keep::Buf(buffer.clone()));
997                }
998                (BindingKind::Texture, Resource::Texture(v)) | (BindingKind::StorageTexture, Resource::StorageTexture(v)) => {
999                    let storage = b.kind == BindingKind::StorageTexture;
1000                    let need = if storage { TextureUsage::STORAGE } else { TextureUsage::SAMPLED };
1001                    if !v.usage().contains(need) {
1002                        return Err(GpuError::Invalid("bind group: texture lacks SAMPLED / STORAGE usage"));
1003                    }
1004                    which.push((e.binding, ty, false, imgs.len()));
1005                    imgs.push(vk::DescriptorImageInfo {
1006                        sampler: 0,
1007                        image_view: v.0.h,
1008                        image_layout: if storage { vk::IMAGE_LAYOUT_GENERAL } else { vk::IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL },
1009                    });
1010                    keep.push(Keep::View(v.clone()));
1011                }
1012                (BindingKind::Sampler, Resource::Sampler(s)) => {
1013                    which.push((e.binding, ty, false, imgs.len()));
1014                    imgs.push(vk::DescriptorImageInfo { sampler: s.0.h, image_view: 0, image_layout: 0 });
1015                    keep.push(Keep::Sampler(s.clone()));
1016                }
1017                _ => return Err(GpuError::Invalid("bind group: resource does not match the binding's kind")),
1018            }
1019        }
1020        let (pool, set) = sh.alloc_set(lay.h, &lay.needs)?;
1021        let writes: Vec<vk::WriteDescriptorSet> = which
1022            .iter()
1023            .map(|&(binding, ty, is_buf, i)| vk::WriteDescriptorSet {
1024                s_type: vk::STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
1025                dst_set: set,
1026                dst_binding: binding,
1027                descriptor_count: 1,
1028                descriptor_type: ty,
1029                p_image_info: if is_buf { core::ptr::null() } else { &imgs[i] },
1030                p_buffer_info: if is_buf { &bufs[i] } else { core::ptr::null() },
1031                ..vk::Zero::zero()
1032            })
1033            .collect();
1034        // SAFETY: a fresh set no command buffer uses; the infos live until the call returns.
1035        unsafe { (sh.f.update_descriptor_sets)(sh.dev, writes.len() as u32, writes.as_ptr(), 0, core::ptr::null()) };
1036        Ok(BindGroup(Arc::new(BgInner {
1037            sh: sh.clone(),
1038            set,
1039            pool,
1040            own_pool: false,
1041            layout: lay.h,
1042            _layout: desc.layout.clone(),
1043            _keep: keep,
1044        })))
1045    }
1046}
1047
1048// ---------------------------------------------------------------- bindless (Voll)
1049
1050/// slots of a Bindless table, by binding
1051struct Slots {
1052    keep: [Vec<Option<Keep>>; 4],
1053}
1054
1055/// a large table of textures, samplers and buffers that shaders index (level Voll): binding 0
1056/// sampled textures, 1 samplers, 2 storage buffers, 3 storage textures; slots may be changed
1057/// while command buffers that use the table are pending, as long as those do not use the slot
1058pub struct Bindless {
1059    group: BindGroup,
1060    layout: BindGroupLayout,
1061    counts: [u32; 4],
1062    slots: SpinLock<Slots>,
1063}
1064
1065impl Device {
1066    pub fn create_bindless(&self, desc: &BindlessDesc) -> Result<Bindless> {
1067        let sh = &self.sh;
1068        sh.check()?;
1069        if !sh.features.bindless {
1070            return Err(GpuError::Unsupported("bindless tables (level Voll)"));
1071        }
1072        let l = &sh.limits;
1073        let counts = [desc.textures, desc.samplers, desc.buffers, desc.storage_textures];
1074        let maxes = [l.max_bindless_textures, l.max_bindless_samplers, l.max_bindless_buffers, l.max_bindless_storage_textures];
1075        if counts.iter().zip(maxes.iter()).any(|(c, m)| c > m) {
1076            return Err(GpuError::Invalid("bindless: more slots than Limits::max_bindless_*"));
1077        }
1078        let types = [vk::DESCRIPTOR_TYPE_SAMPLED_IMAGE, vk::DESCRIPTOR_TYPE_SAMPLER, vk::DESCRIPTOR_TYPE_STORAGE_BUFFER,
1079                     vk::DESCRIPTOR_TYPE_STORAGE_IMAGE];
1080        let vb: Vec<vk::DescriptorSetLayoutBinding> = (0..4)
1081            .filter(|&i| counts[i] > 0)
1082            .map(|i| vk::DescriptorSetLayoutBinding {
1083                binding: i as u32,
1084                descriptor_type: types[i],
1085                descriptor_count: counts[i],
1086                stage_flags: vk::SHADER_STAGE_ALL,
1087                p_immutable_samplers: core::ptr::null(),
1088            })
1089            .collect();
1090        if vb.is_empty() {
1091            return Err(GpuError::Invalid("bindless table without slots"));
1092        }
1093        let flags = vec![vk::DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT | vk::DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT
1094            | vk::DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT; vb.len()];
1095        let fi = vk::DescriptorSetLayoutBindingFlagsCreateInfo {
1096            s_type: vk::STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO,
1097            binding_count: flags.len() as u32,
1098            p_binding_flags: flags.as_ptr(),
1099            ..vk::Zero::zero()
1100        };
1101        let ci = vk::DescriptorSetLayoutCreateInfo {
1102            s_type: vk::STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
1103            p_next: &fi as *const _ as *const c_void,
1104            flags: vk::DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT,
1105            binding_count: vb.len() as u32,
1106            p_bindings: vb.as_ptr(),
1107        };
1108        let mut lh = 0;
1109        // SAFETY: valid create info with its chain.
1110        sh.vk(unsafe { (sh.f.create_descriptor_set_layout)(sh.dev, &ci, core::ptr::null(), &mut lh) })?;
1111        let layout = BindGroupLayout(Arc::new(BglInner { sh: sh.clone(), h: lh, bindings: Vec::new(), needs: Vec::new() }));
1112        let sizes: Vec<vk::DescriptorPoolSize> =
1113            vb.iter().map(|b| vk::DescriptorPoolSize { type_: b.descriptor_type, descriptor_count: b.descriptor_count }).collect();
1114        let pi = vk::DescriptorPoolCreateInfo {
1115            s_type: vk::STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
1116            flags: vk::DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT,
1117            max_sets: 1,
1118            pool_size_count: sizes.len() as u32,
1119            p_pool_sizes: sizes.as_ptr(),
1120            ..vk::Zero::zero()
1121        };
1122        let mut pool = 0;
1123        // SAFETY: valid create info.
1124        sh.vk(unsafe { (sh.f.create_descriptor_pool)(sh.dev, &pi, core::ptr::null(), &mut pool) })?;
1125        let ai = vk::DescriptorSetAllocateInfo {
1126            s_type: vk::STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
1127            descriptor_pool: pool,
1128            descriptor_set_count: 1,
1129            p_set_layouts: &lh,
1130            ..vk::Zero::zero()
1131        };
1132        let mut set = 0;
1133        // SAFETY: one set of the layout from its own pool.
1134        if let Err(e) = sh.vk(unsafe { (sh.f.allocate_descriptor_sets)(sh.dev, &ai, &mut set) }) {
1135            sh.defer(Garbage::Pool(pool));
1136            return Err(e);
1137        }
1138        let group = BindGroup(Arc::new(BgInner {
1139            sh: sh.clone(),
1140            set,
1141            pool,
1142            own_pool: true,
1143            layout: lh,
1144            _layout: layout.clone(),
1145            _keep: Vec::new(),
1146        }));
1147        let slots = Slots { keep: counts.map(|n| vec![None; n as usize]) };
1148        Ok(Bindless { group, layout, counts, slots: SpinLock::new(slots) })
1149    }
1150}
1151
1152impl Bindless {
1153    /// the table as a bind group (CommandEncoder::set_bind_group)
1154    pub fn group(&self) -> &BindGroup {
1155        &self.group
1156    }
1157    /// its layout (PipelineLayoutDesc::groups)
1158    pub fn layout(&self) -> &BindGroupLayout {
1159        &self.layout
1160    }
1161    /// slots per binding: textures, samplers, buffers, storage textures
1162    pub fn counts(&self) -> [u32; 4] {
1163        self.counts
1164    }
1165
1166    fn write(&self, binding: usize, slot: u32, keep: Keep, img: Option<vk::DescriptorImageInfo>,
1167             buf: Option<vk::DescriptorBufferInfo>) -> Result<()> {
1168        let sh = &self.group.0.sh;
1169        sh.check()?;
1170        if slot >= self.counts[binding] {
1171            return Err(GpuError::Invalid("bindless: slot outside the table"));
1172        }
1173        let types = [vk::DESCRIPTOR_TYPE_SAMPLED_IMAGE, vk::DESCRIPTOR_TYPE_SAMPLER, vk::DESCRIPTOR_TYPE_STORAGE_BUFFER,
1174                     vk::DESCRIPTOR_TYPE_STORAGE_IMAGE];
1175        let w = vk::WriteDescriptorSet {
1176            s_type: vk::STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
1177            dst_set: self.group.0.set,
1178            dst_binding: binding as u32,
1179            dst_array_element: slot,
1180            descriptor_count: 1,
1181            descriptor_type: types[binding],
1182            p_image_info: img.as_ref().map_or(core::ptr::null(), |i| i as *const _),
1183            p_buffer_info: buf.as_ref().map_or(core::ptr::null(), |b| b as *const _),
1184            ..vk::Zero::zero()
1185        };
1186        let mut slots = self.slots.lock();
1187        // SAFETY: the set is update-after-bind; the lock serialises updates of it.
1188        unsafe { (sh.f.update_descriptor_sets)(sh.dev, 1, &w, 0, core::ptr::null()) };
1189        slots.keep[binding][slot as usize] = Some(keep); // the old resource is destroyed later
1190        Ok(())
1191    }
1192
1193    /// slot of binding 0: a sampled texture (Layout::Sampled when shaders read it)
1194    pub fn set_texture(&self, slot: u32, view: &TextureView) -> Result<()> {
1195        if !view.usage().contains(TextureUsage::SAMPLED) {
1196            return Err(GpuError::Invalid("bindless: texture lacks SAMPLED usage"));
1197        }
1198        let i = vk::DescriptorImageInfo { sampler: 0, image_view: view.0.h, image_layout: vk::IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL };
1199        self.write(0, slot, Keep::View(view.clone()), Some(i), None)
1200    }
1201
1202    /// slot of binding 1: a sampler
1203    pub fn set_sampler(&self, slot: u32, s: &Sampler) -> Result<()> {
1204        let i = vk::DescriptorImageInfo { sampler: s.0.h, image_view: 0, image_layout: 0 };
1205        self.write(1, slot, Keep::Sampler(s.clone()), Some(i), None)
1206    }
1207
1208    /// slot of binding 2: a storage buffer range (size 0: to the end)
1209    pub fn set_buffer(&self, slot: u32, buf: &Buffer, offset: u64, size: u64) -> Result<()> {
1210        let sh = &self.group.0.sh;
1211        let range = if size == 0 { buf.size().saturating_sub(offset) } else { size };
1212        let align = sh.limits.min_storage_offset_alignment.max(1) as u64;
1213        if !buf.usage().contains(BufferUsage::STORAGE) || offset % align != 0 || range == 0 || offset + range > buf.size() {
1214            return Err(GpuError::Invalid("bindless: buffer range (usage, alignment, size)"));
1215        }
1216        let b = vk::DescriptorBufferInfo { buffer: buf.handle(), offset, range };
1217        self.write(2, slot, Keep::Buf(buf.clone()), None, Some(b))
1218    }
1219
1220    /// slot of binding 3: a storage texture (Layout::General when shaders use it)
1221    pub fn set_storage_texture(&self, slot: u32, view: &TextureView) -> Result<()> {
1222        if !view.usage().contains(TextureUsage::STORAGE) {
1223            return Err(GpuError::Invalid("bindless: texture lacks STORAGE usage"));
1224        }
1225        let i = vk::DescriptorImageInfo { sampler: 0, image_view: view.0.h, image_layout: vk::IMAGE_LAYOUT_GENERAL };
1226        self.write(3, slot, Keep::View(view.clone()), Some(i), None)
1227    }
1228}
1229
1230// ---------------------------------------------------------------- pipelines
1231
1232pub(crate) struct PipeInner {
1233    sh: Arc<Shared>,
1234    pub h: vk::Handle,
1235    pub layout: PipelineLayout,
1236    pub colors: [u32; COLOR_TARGETS_MAX],
1237    pub ncolor: usize,
1238    pub depth: u32,
1239}
1240
1241impl Drop for PipeInner {
1242    fn drop(&mut self) {
1243        self.sh.defer(Garbage::Pipeline(self.h));
1244    }
1245}
1246
1247/// a compute shader with its layout
1248#[derive(Clone)]
1249pub struct ComputePipeline(pub(crate) Arc<PipeInner>);
1250
1251/// vertex + fragment shaders with their fixed state and target formats
1252#[derive(Clone)]
1253pub struct RenderPipeline(pub(crate) Arc<PipeInner>);
1254
1255impl Device {
1256    pub fn create_compute_pipeline(&self, desc: &ComputePipelineDesc) -> Result<ComputePipeline> {
1257        self.compute_pipeline(desc, 0)
1258    }
1259
1260    /// create_compute_pipeline with a required subgroup size (WGSL @subgroup_size, what
1261    /// wgsl::Output::subgroup_size returns): Features::subgroup_size_control, a power of two in
1262    /// Limits::subgroup_min_size ..= subgroup_max_size; size 0: the driver's choice
1263    pub fn create_compute_pipeline_sized(&self, desc: &ComputePipelineDesc, size: u32) -> Result<ComputePipeline> {
1264        if size != 0 {
1265            if !self.sh.features.subgroup_size_control {
1266                return Err(GpuError::Unsupported("subgroup size control"));
1267            }
1268            let lim = &self.sh.limits;
1269            if !size.is_power_of_two() || size < lim.subgroup_min_size || size > lim.subgroup_max_size {
1270                return Err(GpuError::Invalid("the required subgroup size is not a power of two within Limits::subgroup_min_size ..= subgroup_max_size"));
1271            }
1272        }
1273        self.compute_pipeline(desc, size)
1274    }
1275
1276    fn compute_pipeline(&self, desc: &ComputePipelineDesc, size: u32) -> Result<ComputePipeline> {
1277        let sh = &self.sh;
1278        sh.check()?;
1279        let (map, data, set) = stage_info(sh, &desc.shader, shader::Model::GLCompute)?;
1280        // the workgroup size once specialized: at least 1, within the limits (WebGPU checks it here too)
1281        let wg = shader::reflect::workgroup_size(&desc.shader.module.0.words, desc.shader.entry, &set)
1282            .map_err(|_| GpuError::Invalid("pipeline constants: a specialization constant is undefined (division by 0, shift)"))?;
1283        let Some(wg) = wg else { return Err(GpuError::Invalid("compute entry point without a workgroup size")) };
1284        let lim = &sh.limits;
1285        let count = wg.iter().map(|&x| x as u64).product::<u64>();
1286        if wg.contains(&0) || (0..3).any(|i| wg[i] > lim.max_compute_workgroup_size[i]) || count > lim.max_compute_invocations as u64 {
1287            return Err(GpuError::Invalid("workgroup size 0 or beyond Limits::max_compute_workgroup_size / max_compute_invocations"));
1288        }
1289        let si = vk::SpecializationInfo {
1290            map_entry_count: map.len() as u32,
1291            p_map_entries: map.as_ptr(),
1292            data_size: data.len(),
1293            p_data: data.as_ptr() as *const c_void,
1294        };
1295        let name = nul_name(desc.shader.entry);
1296        if size != 0 && wg[0] % size != 0 {
1297            return Err(GpuError::Invalid("the workgroup size in x is not a multiple of the required subgroup size"));
1298        }
1299        let required = vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo {
1300            s_type: vk::STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO,
1301            p_next: core::ptr::null_mut(),
1302            required_subgroup_size: size,
1303        };
1304        let ci = vk::ComputePipelineCreateInfo {
1305            s_type: vk::STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
1306            stage: vk::PipelineShaderStageCreateInfo {
1307                s_type: vk::STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
1308                p_next: if size != 0 { &required as *const _ as *const c_void } else { core::ptr::null() },
1309                stage: vk::SHADER_STAGE_COMPUTE_BIT,
1310                module: desc.shader.module.0.h,
1311                p_name: name.as_ptr(),
1312                p_specialization_info: if map.is_empty() { core::ptr::null() } else { &si as *const _ as *const c_void },
1313                ..vk::Zero::zero()
1314            },
1315            layout: desc.layout.0.h,
1316            base_pipeline_index: -1,
1317            ..vk::Zero::zero()
1318        };
1319        let mut h = 0;
1320        // SAFETY: valid create info; module and layout are alive.
1321        sh.vk(unsafe { (sh.f.create_compute_pipelines)(sh.dev, 0, 1, &ci, core::ptr::null(), &mut h) })?;
1322        Ok(ComputePipeline(Arc::new(PipeInner {
1323            sh: sh.clone(),
1324            h,
1325            layout: desc.layout.clone(),
1326            colors: [0; COLOR_TARGETS_MAX],
1327            ncolor: 0,
1328            depth: 0,
1329        })))
1330    }
1331
1332    pub fn create_render_pipeline(&self, desc: &RenderPipelineDesc) -> Result<RenderPipeline> {
1333        let sh = &self.sh;
1334        sh.check()?;
1335        if desc.targets.len() > sh.limits.max_color_targets as usize {
1336            return Err(GpuError::Invalid("too many colour targets"));
1337        }
1338        if desc.targets.iter().any(|t| t.format.is_depth() || t.format.is_compressed()) {
1339            return Err(GpuError::Invalid("colour target with a depth or compressed format"));
1340        }
1341        if desc.depth.is_some_and(|d| !d.format.is_depth()) {
1342            return Err(GpuError::Invalid("depth state with a colour format"));
1343        }
1344        let (vmap, vdata, _) = stage_info(sh, &desc.vertex, shader::Model::Vertex)?;
1345        let (fmap, fdata, _) = match &desc.fragment {
1346            Some(fs) => stage_info(sh, fs, shader::Model::Fragment)?,
1347            None => (Vec::new(), Vec::new(), Vec::new()),
1348        };
1349        let spec = |map: &Vec<vk::SpecializationMapEntry>, data: &Vec<u8>| vk::SpecializationInfo {
1350            map_entry_count: map.len() as u32,
1351            p_map_entries: map.as_ptr(),
1352            data_size: data.len(),
1353            p_data: data.as_ptr() as *const c_void,
1354        };
1355        let (vsi, fsi) = (spec(&vmap, &vdata), spec(&fmap, &fdata));
1356        let ptr = |map: &Vec<vk::SpecializationMapEntry>, si: &vk::SpecializationInfo| {
1357            if map.is_empty() { core::ptr::null() } else { si as *const _ as *const c_void }
1358        };
1359        let vname = nul_name(desc.vertex.entry);
1360        let fname = nul_name(desc.fragment.map_or("", |f| f.entry));
1361        let mut stages = vec![vk::PipelineShaderStageCreateInfo {
1362            s_type: vk::STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
1363            stage: vk::SHADER_STAGE_VERTEX_BIT,
1364            module: desc.vertex.module.0.h,
1365            p_name: vname.as_ptr(),
1366            p_specialization_info: ptr(&vmap, &vsi),
1367            ..vk::Zero::zero()
1368        }];
1369        if let Some(fs) = desc.fragment {
1370            stages.push(vk::PipelineShaderStageCreateInfo {
1371                s_type: vk::STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
1372                stage: vk::SHADER_STAGE_FRAGMENT_BIT,
1373                module: fs.module.0.h,
1374                p_name: fname.as_ptr(),
1375                p_specialization_info: ptr(&fmap, &fsi),
1376                ..vk::Zero::zero()
1377            });
1378        }
1379        let mut vbind = Vec::new();
1380        let mut vattr = Vec::new();
1381        for (i, b) in desc.buffers.iter().enumerate() {
1382            vbind.push(vk::VertexInputBindingDescription {
1383                binding: i as u32,
1384                stride: b.stride,
1385                input_rate: if b.per_instance { vk::VERTEX_INPUT_RATE_INSTANCE } else { vk::VERTEX_INPUT_RATE_VERTEX },
1386            });
1387            for a in b.attributes {
1388                vattr.push(vk::VertexInputAttributeDescription {
1389                    location: a.location,
1390                    binding: i as u32,
1391                    format: match a.format {
1392                        VertexFormat::F32 => vk::FORMAT_R32_SFLOAT,
1393                        VertexFormat::F32x2 => vk::FORMAT_R32G32_SFLOAT,
1394                        VertexFormat::F32x3 => vk::FORMAT_R32G32B32_SFLOAT,
1395                        VertexFormat::F32x4 => vk::FORMAT_R32G32B32A32_SFLOAT,
1396                        VertexFormat::U32 => vk::FORMAT_R32_UINT,
1397                        VertexFormat::U32x2 => vk::FORMAT_R32G32_UINT,
1398                        VertexFormat::U32x4 => vk::FORMAT_R32G32B32A32_UINT,
1399                        VertexFormat::Unorm8x4 => vk::FORMAT_R8G8B8A8_UNORM,
1400                    },
1401                    offset: a.offset,
1402                });
1403            }
1404        }
1405        if vbind.len() > sh.limits.max_vertex_buffers as usize || vattr.len() > sh.limits.max_vertex_attributes as usize {
1406            return Err(GpuError::Invalid("too many vertex buffers or attributes"));
1407        }
1408        let vi = vk::PipelineVertexInputStateCreateInfo {
1409            s_type: vk::STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
1410            vertex_binding_description_count: vbind.len() as u32,
1411            p_vertex_binding_descriptions: vbind.as_ptr(),
1412            vertex_attribute_description_count: vattr.len() as u32,
1413            p_vertex_attribute_descriptions: vattr.as_ptr(),
1414            ..vk::Zero::zero()
1415        };
1416        let ia = vk::PipelineInputAssemblyStateCreateInfo {
1417            s_type: vk::STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
1418            topology: match desc.topology {
1419                Topology::Points => vk::PRIMITIVE_TOPOLOGY_POINT_LIST,
1420                Topology::Lines => vk::PRIMITIVE_TOPOLOGY_LINE_LIST,
1421                Topology::LineStrip => vk::PRIMITIVE_TOPOLOGY_LINE_STRIP,
1422                Topology::Triangles => vk::PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
1423                Topology::TriangleStrip => vk::PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
1424            },
1425            ..vk::Zero::zero()
1426        };
1427        let vp = vk::PipelineViewportStateCreateInfo {
1428            s_type: vk::STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
1429            viewport_count: 1,
1430            scissor_count: 1,
1431            ..vk::Zero::zero()
1432        };
1433        let rs = vk::PipelineRasterizationStateCreateInfo {
1434            s_type: vk::STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
1435            polygon_mode: vk::POLYGON_MODE_FILL,
1436            cull_mode: match desc.cull {
1437                Cull::None => vk::CULL_MODE_NONE,
1438                Cull::Front => vk::CULL_MODE_FRONT_BIT,
1439                Cull::Back => vk::CULL_MODE_BACK_BIT,
1440            },
1441            front_face: if desc.front_cw { vk::FRONT_FACE_CLOCKWISE } else { vk::FRONT_FACE_COUNTER_CLOCKWISE },
1442            line_width: 1.0,
1443            ..vk::Zero::zero()
1444        };
1445        let ms = vk::PipelineMultisampleStateCreateInfo {
1446            s_type: vk::STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
1447            rasterization_samples: vk::SAMPLE_COUNT_1_BIT,
1448            min_sample_shading: 1.0,
1449            ..vk::Zero::zero()
1450        };
1451        let ds = vk::PipelineDepthStencilStateCreateInfo {
1452            s_type: vk::STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
1453            depth_test_enable: desc.depth.is_some() as u32,
1454            depth_write_enable: desc.depth.is_some_and(|d| d.write) as u32,
1455            depth_compare_op: desc.depth.map_or(vk::COMPARE_OP_ALWAYS, |d| compare_op(d.compare)),
1456            max_depth_bounds: 1.0,
1457            ..vk::Zero::zero()
1458        };
1459        let factor = |f: BlendFactor| match f {
1460            BlendFactor::Zero => vk::BLEND_FACTOR_ZERO,
1461            BlendFactor::One => vk::BLEND_FACTOR_ONE,
1462            BlendFactor::Src => vk::BLEND_FACTOR_SRC_COLOR,
1463            BlendFactor::OneMinusSrc => vk::BLEND_FACTOR_ONE_MINUS_SRC_COLOR,
1464            BlendFactor::Dst => vk::BLEND_FACTOR_DST_COLOR,
1465            BlendFactor::OneMinusDst => vk::BLEND_FACTOR_ONE_MINUS_DST_COLOR,
1466            BlendFactor::SrcAlpha => vk::BLEND_FACTOR_SRC_ALPHA,
1467            BlendFactor::OneMinusSrcAlpha => vk::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
1468            BlendFactor::DstAlpha => vk::BLEND_FACTOR_DST_ALPHA,
1469            BlendFactor::OneMinusDstAlpha => vk::BLEND_FACTOR_ONE_MINUS_DST_ALPHA,
1470        };
1471        let op = |o: BlendOp| match o {
1472            BlendOp::Add => vk::BLEND_OP_ADD,
1473            BlendOp::Subtract => vk::BLEND_OP_SUBTRACT,
1474            BlendOp::ReverseSubtract => vk::BLEND_OP_REVERSE_SUBTRACT,
1475            BlendOp::Min => vk::BLEND_OP_MIN,
1476            BlendOp::Max => vk::BLEND_OP_MAX,
1477        };
1478        let att: Vec<vk::PipelineColorBlendAttachmentState> = desc
1479            .targets
1480            .iter()
1481            .map(|t| {
1482                let b = t.blend.unwrap_or(Blend::ADD);
1483                vk::PipelineColorBlendAttachmentState {
1484                    blend_enable: t.blend.is_some() as u32,
1485                    src_color_blend_factor: factor(b.color.src),
1486                    dst_color_blend_factor: factor(b.color.dst),
1487                    color_blend_op: op(b.color.op),
1488                    src_alpha_blend_factor: factor(b.alpha.src),
1489                    dst_alpha_blend_factor: factor(b.alpha.dst),
1490                    alpha_blend_op: op(b.alpha.op),
1491                    color_write_mask: t.write_mask & 0xF,
1492                }
1493            })
1494            .collect();
1495        let cb = vk::PipelineColorBlendStateCreateInfo {
1496            s_type: vk::STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
1497            attachment_count: att.len() as u32,
1498            p_attachments: att.as_ptr(),
1499            ..vk::Zero::zero()
1500        };
1501        let dyns = [vk::DYNAMIC_STATE_VIEWPORT, vk::DYNAMIC_STATE_SCISSOR];
1502        let dy = vk::PipelineDynamicStateCreateInfo {
1503            s_type: vk::STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
1504            dynamic_state_count: 2,
1505            p_dynamic_states: dyns.as_ptr(),
1506            ..vk::Zero::zero()
1507        };
1508        let mut colors = [0u32; COLOR_TARGETS_MAX];
1509        for (i, t) in desc.targets.iter().enumerate() {
1510            colors[i] = vk_format(t.format);
1511        }
1512        let depth = desc.depth.map_or(vk::FORMAT_UNDEFINED, |d| vk_format(d.format));
1513        let stencil = desc.depth.filter(|d| d.format.has_stencil()).map_or(vk::FORMAT_UNDEFINED, |d| vk_format(d.format));
1514        let ri = vk::PipelineRenderingCreateInfo {
1515            s_type: vk::STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
1516            color_attachment_count: desc.targets.len() as u32,
1517            p_color_attachment_formats: colors.as_ptr(),
1518            depth_attachment_format: depth,
1519            stencil_attachment_format: stencil,
1520            ..vk::Zero::zero()
1521        };
1522        let gi = vk::GraphicsPipelineCreateInfo {
1523            s_type: vk::STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
1524            p_next: &ri as *const _ as *const c_void,
1525            stage_count: stages.len() as u32,
1526            p_stages: stages.as_ptr(),
1527            p_vertex_input_state: &vi,
1528            p_input_assembly_state: &ia,
1529            p_viewport_state: &vp,
1530            p_rasterization_state: &rs,
1531            p_multisample_state: &ms,
1532            p_depth_stencil_state: &ds,
1533            p_color_blend_state: &cb,
1534            p_dynamic_state: &dy,
1535            layout: desc.layout.0.h,
1536            base_pipeline_index: -1,
1537            ..vk::Zero::zero()
1538        };
1539        let mut h = 0;
1540        // SAFETY: every structure of the create info lives until the call returns.
1541        sh.vk(unsafe { (sh.f.create_graphics_pipelines)(sh.dev, 0, 1, &gi, core::ptr::null(), &mut h) })?;
1542        Ok(RenderPipeline(Arc::new(PipeInner {
1543            sh: sh.clone(),
1544            h,
1545            layout: desc.layout.clone(),
1546            colors,
1547            ncolor: desc.targets.len(),
1548            depth,
1549        })))
1550    }
1551}
1552
1553// ---------------------------------------------------------------- timestamps
1554
1555pub(crate) struct TsInner {
1556    sh: Arc<Shared>,
1557    pub pool: vk::Handle,
1558    pub count: u32,
1559}
1560
1561impl Drop for TsInner {
1562    fn drop(&mut self) {
1563        self.sh.defer(Garbage::Query(self.pool));
1564    }
1565}
1566
1567/// GPU timestamps (CommandEncoder::reset_timestamps / timestamp)
1568#[derive(Clone)]
1569pub struct Timestamps(pub(crate) Arc<TsInner>);
1570
1571impl Device {
1572    pub fn create_timestamps(&self, count: u32) -> Result<Timestamps> {
1573        let sh = &self.sh;
1574        sh.check()?;
1575        if !sh.features.timestamps {
1576            return Err(GpuError::Unsupported("timestamps"));
1577        }
1578        if count == 0 {
1579            return Err(GpuError::Invalid("0 timestamps"));
1580        }
1581        let ci = vk::QueryPoolCreateInfo {
1582            s_type: vk::STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
1583            query_type: vk::QUERY_TYPE_TIMESTAMP,
1584            query_count: count,
1585            ..vk::Zero::zero()
1586        };
1587        let mut pool = 0;
1588        // SAFETY: valid create info.
1589        sh.vk(unsafe { (sh.f.create_query_pool)(sh.dev, &ci, core::ptr::null(), &mut pool) })?;
1590        let ts = Timestamps(Arc::new(TsInner { sh: sh.clone(), pool, count }));
1591        // queries start reset (unavailable: read_* gives NotReady until they are written): from
1592        // the host where the device allows it, else first thing in the next submission
1593        match sh.f.reset_query_pool.filter(|_| sh.host_query_reset) {
1594            // SAFETY: a new pool no command uses yet.
1595            Some(reset) => unsafe { reset(sh.dev, pool, 0, count) },
1596            None => {
1597                let mut st = sh.st.lock();
1598                let cb = sh.upload_cb(&mut st)?;
1599                // SAFETY: the upload buffer is recording outside a render pass.
1600                unsafe { (sh.f.cmd_reset_query_pool)(cb, pool, 0, count) };
1601            }
1602        }
1603        Ok(ts)
1604    }
1605}
1606
1607impl Timestamps {
1608    pub fn count(&self) -> u32 {
1609        self.0.count
1610    }
1611
1612    /// the raw ticks of timestamps first .. first + n (Err(NotReady): not all written and done)
1613    pub fn read_ticks(&self, first: u32, n: u32) -> Result<Vec<u64>> {
1614        let t = &self.0;
1615        t.sh.check()?;
1616        if n == 0 || first.checked_add(n).is_none_or(|e| e > t.count) {
1617            return Err(GpuError::Invalid("timestamps outside the set"));
1618        }
1619        let mut raw = vec![[0u64; 2]; n as usize];
1620        // SAFETY: raw holds n (value, availability) pairs of 16 bytes.
1621        let r = unsafe {
1622            (t.sh.f.get_query_pool_results)(t.sh.dev, t.pool, first, n, raw.len() * 16, raw.as_mut_ptr() as *mut c_void, 16,
1623                vk::QUERY_RESULT_64_BIT | vk::QUERY_RESULT_WITH_AVAILABILITY_BIT)
1624        };
1625        if r == vk::NOT_READY {
1626            return Err(GpuError::NotReady);
1627        }
1628        t.sh.vk(r)?;
1629        if raw.iter().any(|p| p[1] == 0) {
1630            return Err(GpuError::NotReady);
1631        }
1632        Ok(raw.iter().map(|p| p[0] & t.sh.ts_mask).collect())
1633    }
1634
1635    /// timestamps first .. first + n in milliseconds on the GPU's clock, counted from the first
1636    /// timestamp the device read (so f64 keeps every tick: absolute ticks reach 10^18 ns)
1637    pub fn read_ms(&self, first: u32, n: u32) -> Result<Vec<f64>> {
1638        let ticks = self.read_ticks(first, n)?;
1639        let sh = &self.0.sh;
1640        let low = ticks.iter().copied().min().unwrap_or(0).max(1);
1641        let base = match sh.ts_base.compare_exchange(0, low, core::sync::atomic::Ordering::AcqRel,
1642                                                     core::sync::atomic::Ordering::Acquire) {
1643            Ok(_) => low,
1644            Err(b) => b,
1645        };
1646        let period = sh.limits.timestamp_period_ns as f64;
1647        Ok(ticks.into_iter().map(|t| (t as i128 - base as i128) as f64 * period / 1.0e6).collect())
1648    }
1649}