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