Skip to main content

gpu/
vk_cmd.rs

1// vk_cmd -- command recording: CommandEncoder -> CommandBuffer
2//
3//   device.encoder() -> Result<CommandEncoder>      a command buffer slot, begun
4//   copies (outside render passes): copy_buffer, fill_buffer, copy_buffer_to_texture (the
5//        region in Layout::CopyDst), copy_texture_to_buffer (Layout::CopySrc)
6//   barrier(before: Access, after: Access)          a global memory barrier
7//   texture_barrier(tex, before, after) / texture_barrier_range(.., mips, layers, ..)
8//   set_compute_pipeline, set_bind_group(g, &group), push_constants(off, &[T]),
9//        dispatch(x, y, z), dispatch_indirect(&buf, off)
10//   begin_render(&RenderPassDesc) .. end_render()   dynamic rendering; the attachments go from
11//        `before` to ColorTarget / DepthTarget at the start and to `after` at the end; viewport
12//        and scissor = the whole target
13//   set_render_pipeline, set_viewport, set_scissor, set_vertex_buffer, set_index_buffer,
14//        draw, draw_indexed, draw_indirect, draw_indexed_indirect (Basis), and on Voll
15//        draw_indexed_indirect_multi and draw_indexed_indirect_count
16//   reset_timestamps(&ts, first, n) (outside passes), timestamp(&ts, i)
17//   finish() -> Result<CommandBuffer>               the first misuse recorded, if any
18//
19// Bind groups and push constants go to the pipeline set last: the compute pipeline outside
20// render passes, the render pipeline inside (set the pipeline first).  Every command checks
21// what the backend can know cheaply (the pipeline and pass state, bind group / pipeline layout
22// compatibility, target formats, buffer usages, ranges and alignments, the Voll features); a
23// failed check skips the command and finish() returns it as GpuError::Invalid.
24//
25// Stages and accesses per Layout (texture_barrier, attachments, write_texture), as (source;
26// destination): Undefined (colour output + copy + compute, no access -- the stages the
27// swapchain acquisition waits at; not a destination), General (all commands, memory read /
28// write), CopySrc / CopyDst (transfer), Sampled (vertex + fragment + compute shaders), ColorTarget
29// (colour output), DepthTarget (early + late fragment tests), DepthRead (fragment tests and
30// shaders), Present (as Undefined; bottom of pipe).
31// Viewport: y flipped (origin top left, NDC y up: lib.rs conventions).
32
33use alloc::vec::Vec;
34use core::marker::PhantomData;
35
36use crate::vk_dev::{Device, Shared, Slot};
37use crate::vk_ffi as vk;
38use crate::vk_res::{PipeInner, copy_region, subresources};
39use crate::*;
40
41/// what an image handed to the window is in: Linux has no VK_KHR_swapchain, vk_dmabuf.rs hands
42/// dma-bufs over in GENERAL
43const LAYOUT_PRESENT: u32 = if cfg!(jfgui_glibc) { vk::IMAGE_LAYOUT_GENERAL } else { vk::IMAGE_LAYOUT_PRESENT_SRC_KHR };
44
45/// (vk layout, stages, access) of a layout as the source of a barrier
46fn src_of(l: Layout) -> (u32, u32, u32) {
47    let shaders = vk::PIPELINE_STAGE_VERTEX_SHADER_BIT | vk::PIPELINE_STAGE_FRAGMENT_SHADER_BIT | vk::PIPELINE_STAGE_COMPUTE_SHADER_BIT;
48    let tests = vk::PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | vk::PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
49    let acquire = vk::PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | vk::PIPELINE_STAGE_TRANSFER_BIT | vk::PIPELINE_STAGE_COMPUTE_SHADER_BIT;
50    match l {
51        Layout::Undefined => (vk::IMAGE_LAYOUT_UNDEFINED, acquire, 0),
52        Layout::General => (vk::IMAGE_LAYOUT_GENERAL, vk::PIPELINE_STAGE_ALL_COMMANDS_BIT, vk::ACCESS_MEMORY_WRITE_BIT),
53        Layout::CopySrc => (vk::IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, vk::PIPELINE_STAGE_TRANSFER_BIT, 0),
54        Layout::CopyDst => (vk::IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, vk::PIPELINE_STAGE_TRANSFER_BIT, vk::ACCESS_TRANSFER_WRITE_BIT),
55        Layout::Sampled => (vk::IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, shaders, 0),
56        Layout::ColorTarget => (vk::IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, vk::PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
57                                vk::ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
58        Layout::DepthTarget => (vk::IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, tests, vk::ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT),
59        Layout::DepthRead => (vk::IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, tests | shaders, 0),
60        Layout::Present => (LAYOUT_PRESENT, acquire, 0),
61    }
62}
63
64/// (vk layout, stages, access) of a layout as the destination of a barrier
65fn dst_of(l: Layout) -> (u32, u32, u32) {
66    let shaders = vk::PIPELINE_STAGE_VERTEX_SHADER_BIT | vk::PIPELINE_STAGE_FRAGMENT_SHADER_BIT | vk::PIPELINE_STAGE_COMPUTE_SHADER_BIT;
67    let tests = vk::PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | vk::PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
68    match l {
69        Layout::Undefined => (vk::IMAGE_LAYOUT_UNDEFINED, vk::PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0),
70        Layout::General => (vk::IMAGE_LAYOUT_GENERAL, vk::PIPELINE_STAGE_ALL_COMMANDS_BIT,
71                            vk::ACCESS_MEMORY_READ_BIT | vk::ACCESS_MEMORY_WRITE_BIT),
72        Layout::CopySrc => (vk::IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, vk::PIPELINE_STAGE_TRANSFER_BIT, vk::ACCESS_TRANSFER_READ_BIT),
73        Layout::CopyDst => (vk::IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, vk::PIPELINE_STAGE_TRANSFER_BIT, vk::ACCESS_TRANSFER_WRITE_BIT),
74        Layout::Sampled => (vk::IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, shaders, vk::ACCESS_SHADER_READ_BIT),
75        Layout::ColorTarget => (vk::IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, vk::PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
76                                vk::ACCESS_COLOR_ATTACHMENT_READ_BIT | vk::ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
77        Layout::DepthTarget => (vk::IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, tests,
78                                vk::ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | vk::ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT),
79        Layout::DepthRead => (vk::IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, tests | shaders,
80                              vk::ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | vk::ACCESS_SHADER_READ_BIT),
81        Layout::Present => (LAYOUT_PRESENT, vk::PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0),
82    }
83}
84
85fn image_barrier_info(image: vk::Handle, range: vk::ImageSubresourceRange, before: Layout, after: Layout)
86                      -> (vk::ImageMemoryBarrier, u32, u32) {
87    let (ol, ss, sa) = src_of(before);
88    let (nl, ds, da) = dst_of(after);
89    let b = vk::ImageMemoryBarrier {
90        s_type: vk::STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
91        src_access_mask: sa,
92        dst_access_mask: da,
93        old_layout: ol,
94        new_layout: nl,
95        src_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
96        dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
97        image,
98        subresource_range: range,
99        ..vk::Zero::zero()
100    };
101    (b, ss, ds)
102}
103
104/// record one image layout transition
105///
106/// # Safety
107/// cb is recording outside a render pass; image belongs to f's device.
108pub(crate) unsafe fn image_barrier(f: &vk::DeviceFns, cb: vk::CommandBuffer, image: vk::Handle, range: vk::ImageSubresourceRange,
109                                   before: Layout, after: Layout) {
110    let (b, ss, ds) = image_barrier_info(image, range, before, after);
111    // SAFETY: the caller's contract.
112    unsafe { (f.cmd_pipeline_barrier)(cb, ss, ds, 0, 0, core::ptr::null(), 0, core::ptr::null(), 1, &b) };
113}
114
115/// (stages, source access, destination access) of Access flags
116fn access_of(a: Access) -> (u32, u32, u32) {
117    let table: [(Access, u32, u32, u32); 12] = [
118        (Access::COPY_READ, vk::PIPELINE_STAGE_TRANSFER_BIT, 0, vk::ACCESS_TRANSFER_READ_BIT),
119        (Access::COPY_WRITE, vk::PIPELINE_STAGE_TRANSFER_BIT, vk::ACCESS_TRANSFER_WRITE_BIT, vk::ACCESS_TRANSFER_WRITE_BIT),
120        (Access::COMPUTE_READ, vk::PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, vk::ACCESS_SHADER_READ_BIT | vk::ACCESS_UNIFORM_READ_BIT),
121        (Access::COMPUTE_WRITE, vk::PIPELINE_STAGE_COMPUTE_SHADER_BIT, vk::ACCESS_SHADER_WRITE_BIT,
122         vk::ACCESS_SHADER_READ_BIT | vk::ACCESS_SHADER_WRITE_BIT),
123        (Access::GRAPHICS_READ, vk::PIPELINE_STAGE_VERTEX_SHADER_BIT | vk::PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
124         vk::ACCESS_SHADER_READ_BIT | vk::ACCESS_UNIFORM_READ_BIT),
125        (Access::GRAPHICS_WRITE, vk::PIPELINE_STAGE_VERTEX_SHADER_BIT | vk::PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
126         vk::ACCESS_SHADER_WRITE_BIT, vk::ACCESS_SHADER_READ_BIT | vk::ACCESS_SHADER_WRITE_BIT),
127        (Access::INDIRECT, vk::PIPELINE_STAGE_DRAW_INDIRECT_BIT, 0, vk::ACCESS_INDIRECT_COMMAND_READ_BIT),
128        (Access::INDEX_VERTEX, vk::PIPELINE_STAGE_VERTEX_INPUT_BIT, 0,
129         vk::ACCESS_INDEX_READ_BIT | vk::ACCESS_VERTEX_ATTRIBUTE_READ_BIT),
130        (Access::COLOR_TARGET, vk::PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, vk::ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
131         vk::ACCESS_COLOR_ATTACHMENT_READ_BIT | vk::ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
132        (Access::DEPTH_TARGET, vk::PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | vk::PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
133         vk::ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
134         vk::ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | vk::ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT),
135        (Access::HOST_READ, vk::PIPELINE_STAGE_HOST_BIT, 0, vk::ACCESS_HOST_READ_BIT),
136        (Access::HOST_WRITE, vk::PIPELINE_STAGE_HOST_BIT, vk::ACCESS_HOST_WRITE_BIT, vk::ACCESS_HOST_WRITE_BIT),
137    ];
138    let mut out = (0, 0, 0);
139    for (flag, st, sa, da) in table {
140        if a.intersects(flag) {
141            out.0 |= st;
142            out.1 |= sa;
143            out.2 |= da;
144        }
145    }
146    out
147}
148
149/// an attachment's transition at the end of a pass
150struct EndBarrier {
151    image: vk::Handle,
152    range: vk::ImageSubresourceRange,
153    from: Layout,
154    to: Layout,
155}
156
157struct Pass {
158    colors: [u32; COLOR_TARGETS_MAX],
159    n: usize,
160    depth: u32,
161    end: Vec<EndBarrier>,
162}
163
164/// records commands; finish() makes a CommandBuffer for Queue::submit
165pub struct CommandEncoder<'a> {
166    sh: &'a Shared,
167    slot: Option<Slot>,
168    cb: vk::CommandBuffer,
169    err: Option<GpuError>,
170    pass: Option<Pass>,
171    compute: Option<&'a PipeInner>,
172    graphics: Option<&'a PipeInner>,
173    index: bool,
174    _borrow: PhantomData<&'a ()>,
175}
176
177/// recorded commands, ready to submit (dropped unsubmitted: discarded)
178pub struct CommandBuffer<'a> {
179    sh: &'a Shared,
180    slot: Option<Slot>,
181}
182
183impl CommandBuffer<'_> {
184    pub(crate) fn into_slot(mut self) -> Slot {
185        self.slot.take().unwrap_or_else(|| unreachable!())
186    }
187}
188
189impl Drop for CommandBuffer<'_> {
190    fn drop(&mut self) {
191        if let Some(s) = self.slot.take() {
192            self.sh.give_slot(s);
193        }
194    }
195}
196
197impl Drop for CommandEncoder<'_> {
198    fn drop(&mut self) {
199        if let Some(s) = self.slot.take() {
200            // the buffer is recording (maybe inside a pass) and never submitted: resetting its
201            // pool returns it to the initial state
202            self.sh.give_slot(s);
203        }
204    }
205}
206
207impl Device {
208    /// a command encoder (a recording command buffer)
209    pub fn encoder(&self) -> Result<CommandEncoder<'_>> {
210        let slot = self.sh.take_slot()?;
211        Ok(CommandEncoder {
212            sh: &self.sh,
213            cb: slot.cb,
214            slot: Some(slot),
215            err: None,
216            pass: None,
217            compute: None,
218            graphics: None,
219            index: false,
220            _borrow: PhantomData,
221        })
222    }
223}
224
225impl<'a> CommandEncoder<'a> {
226    /// record the first misuse; Some(()) when the command may be recorded
227    #[inline]
228    fn ok(&mut self, cond: bool, what: &'static str) -> Option<()> {
229        if self.err.is_some() {
230            return None;
231        }
232        if !cond {
233            self.err = Some(GpuError::Invalid(what));
234            return None;
235        }
236        Some(())
237    }
238
239    #[inline]
240    fn outside(&mut self, what: &'static str) -> Option<()> {
241        let c = self.pass.is_none();
242        self.ok(c, what)
243    }
244
245    fn buf_range(&mut self, b: &Buffer, off: u64, n: u64, what: &'static str) -> Option<()> {
246        let c = off.checked_add(n).is_some_and(|e| e <= b.size());
247        self.ok(c, what)
248    }
249
250    // ------------------------------------------------------------ copies
251
252    pub fn copy_buffer(&mut self, src: &'a Buffer, src_off: u64, dst: &'a Buffer, dst_off: u64, size: u64) {
253        if self.outside("copy_buffer inside a render pass").is_none()
254            || self.buf_range(src, src_off, size, "copy_buffer: source range").is_none()
255            || self.buf_range(dst, dst_off, size, "copy_buffer: destination range").is_none()
256            || size == 0
257        {
258            return;
259        }
260        let r = vk::BufferCopy { src_offset: src_off, dst_offset: dst_off, size };
261        // SAFETY: recording; ranges checked.
262        unsafe { (self.sh.f.cmd_copy_buffer)(self.cb, src.handle(), dst.handle(), 1, &r) };
263    }
264
265    /// fill size bytes at offset with a 32-bit value (both multiples of 4)
266    pub fn fill_buffer(&mut self, buf: &'a Buffer, offset: u64, size: u64, value: u32) {
267        if self.outside("fill_buffer inside a render pass").is_none()
268            || self.ok(offset % 4 == 0 && size % 4 == 0 && size > 0, "fill_buffer: offset and size multiples of 4").is_none()
269            || self.buf_range(buf, offset, size, "fill_buffer: range").is_none()
270        {
271            return;
272        }
273        // SAFETY: recording; range checked.
274        unsafe { (self.sh.f.cmd_fill_buffer)(self.cb, buf.handle(), offset, size, value) };
275    }
276
277    /// buffer data -> a texture region (the region in Layout::CopyDst)
278    pub fn copy_buffer_to_texture(&mut self, src: &'a Buffer, layout: DataLayout, dst: TextureRegion<'a>) {
279        if self.outside("copy inside a render pass").is_none() {
280            return;
281        }
282        let c = copy_region(&dst, &layout, src.size());
283        let ok = c.is_ok() && dst.texture.usage().contains(TextureUsage::COPY_DST);
284        if self.ok(ok, "copy_buffer_to_texture: region, data layout or COPY_DST usage").is_none() {
285            return;
286        }
287        let Ok((r, _)) = c else { return };
288        // SAFETY: recording; region checked against texture and buffer.
289        unsafe {
290            (self.sh.f.cmd_copy_buffer_to_image)(self.cb, src.handle(), dst.texture.image(),
291                vk::IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &r)
292        };
293    }
294
295    /// a texture region -> buffer data (the region in Layout::CopySrc)
296    pub fn copy_texture_to_buffer(&mut self, src: TextureRegion<'a>, dst: &'a Buffer, layout: DataLayout) {
297        if self.outside("copy inside a render pass").is_none() {
298            return;
299        }
300        let c = copy_region(&src, &layout, dst.size());
301        let ok = c.is_ok() && src.texture.usage().contains(TextureUsage::COPY_SRC);
302        if self.ok(ok, "copy_texture_to_buffer: region, data layout or COPY_SRC usage").is_none() {
303            return;
304        }
305        let Ok((r, _)) = c else { return };
306        // SAFETY: as above.
307        unsafe {
308            (self.sh.f.cmd_copy_image_to_buffer)(self.cb, src.texture.image(), vk::IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
309                dst.handle(), 1, &r)
310        };
311    }
312
313    // ------------------------------------------------------------ barriers
314
315    /// the accesses `before` finish (and their writes become visible) before the `after` ones
316    pub fn barrier(&mut self, before: Access, after: Access) {
317        if self.outside("barrier inside a render pass").is_none() {
318            return;
319        }
320        let (ss, sa, _) = access_of(before);
321        let (ds, _, da) = access_of(after);
322        let mb = vk::MemoryBarrier { s_type: vk::STRUCTURE_TYPE_MEMORY_BARRIER, src_access_mask: sa, dst_access_mask: da,
323                                     ..vk::Zero::zero() };
324        let ss = if ss == 0 { vk::PIPELINE_STAGE_TOP_OF_PIPE_BIT } else { ss };
325        let ds = if ds == 0 { vk::PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT } else { ds };
326        // SAFETY: recording outside a pass.
327        unsafe { (self.sh.f.cmd_pipeline_barrier)(self.cb, ss, ds, 0, 1, &mb, 0, core::ptr::null(), 0, core::ptr::null()) };
328    }
329
330    /// every mip and layer of tex from `before` to `after`
331    pub fn texture_barrier(&mut self, tex: &'a Texture, before: Layout, after: Layout) {
332        self.texture_barrier_range(tex, 0, tex.mips(), 0, tex.layers(), before, after);
333    }
334
335    /// mips base_mip .. +mips and layers base_layer .. +layers of tex from `before` to `after`
336    #[allow(clippy::too_many_arguments)]
337    pub fn texture_barrier_range(&mut self, tex: &'a Texture, base_mip: u32, mips: u32, base_layer: u32, layers: u32,
338                                 before: Layout, after: Layout) {
339        if self.outside("texture_barrier inside a render pass").is_none()
340            || self.ok(mips > 0 && layers > 0 && base_mip + mips <= tex.mips() && base_layer + layers <= tex.layers(),
341                       "texture_barrier: range outside the texture").is_none()
342            || self.ok(after != Layout::Undefined, "texture_barrier: Undefined is no destination").is_none()
343        {
344            return;
345        }
346        let range = subresources(tex, base_mip, mips, base_layer, layers);
347        // SAFETY: recording outside a pass.
348        unsafe { image_barrier(&self.sh.f, self.cb, tex.image(), range, before, after) };
349    }
350
351    // ------------------------------------------------------------ compute
352
353    pub fn set_compute_pipeline(&mut self, p: &'a ComputePipeline) {
354        if self.outside("set_compute_pipeline inside a render pass").is_none() {
355            return;
356        }
357        self.compute = Some(&p.0);
358        // SAFETY: recording; the pipeline belongs to this device.
359        unsafe { (self.sh.f.cmd_bind_pipeline)(self.cb, vk::PIPELINE_BIND_POINT_COMPUTE, p.0.h) };
360    }
361
362    /// the pipeline bind groups and push constants go to: the render pipeline inside a pass,
363    /// else the compute pipeline
364    fn current(&mut self) -> Option<(&'a PipeInner, u32)> {
365        let (p, bp) = if self.pass.is_some() {
366            (self.graphics, vk::PIPELINE_BIND_POINT_GRAPHICS)
367        } else {
368            (self.compute, vk::PIPELINE_BIND_POINT_COMPUTE)
369        };
370        self.ok(p.is_some(), "bind group / push constants before a pipeline")?;
371        p.map(|p| (p, bp))
372    }
373
374    /// bind group g (= @group(g)) of the current pipeline's layout
375    pub fn set_bind_group(&mut self, index: u32, group: &'a BindGroup) {
376        let Some((p, bp)) = self.current() else { return };
377        let sets = &p.layout.0.sets;
378        let fits = sets.get(index as usize).is_some_and(|&l| l == group.0.layout);
379        if self.ok(fits, "set_bind_group: the group's layout differs from the pipeline layout's").is_none() {
380            return;
381        }
382        // SAFETY: recording; set and layout are compatible (checked).
383        unsafe {
384            (self.sh.f.cmd_bind_descriptor_sets)(self.cb, bp, p.layout.0.h, index, 1, &group.0.set, 0, core::ptr::null())
385        };
386    }
387
388    /// push constants at offset (bytes) for the current pipeline
389    pub fn push_constants<T: Pod>(&mut self, offset: u32, data: &[T]) {
390        let Some((p, _)) = self.current() else { return };
391        let bytes = bytes_of(data);
392        let n = bytes.len() as u32;
393        let fits = offset % 4 == 0 && n % 4 == 0 && n > 0 && offset + n <= p.layout.0.push;
394        if self.ok(fits, "push_constants: range outside the layout's (multiples of 4)").is_none() {
395            return;
396        }
397        // SAFETY: recording; the range lies within the layout's push-constant range.
398        unsafe {
399            (self.sh.f.cmd_push_constants)(self.cb, p.layout.0.h, vk::SHADER_STAGE_ALL, offset, n,
400                bytes.as_ptr() as *const core::ffi::c_void)
401        };
402    }
403
404    pub fn dispatch(&mut self, x: u32, y: u32, z: u32) {
405        let m = self.sh.limits.max_compute_workgroups;
406        let ok = self.pass.is_none() && self.compute.is_some() && x <= m[0] && y <= m[1] && z <= m[2];
407        if self.ok(ok, "dispatch: no compute pipeline, inside a pass or too many workgroups").is_none() {
408            return;
409        }
410        // SAFETY: recording with a compute pipeline bound.
411        unsafe { (self.sh.f.cmd_dispatch)(self.cb, x, y, z) };
412    }
413
414    /// workgroup counts (3 x u32) from buf at offset
415    pub fn dispatch_indirect(&mut self, buf: &'a Buffer, offset: u64) {
416        let ok = self.pass.is_none() && self.compute.is_some() && buf.usage().contains(BufferUsage::INDIRECT) && offset % 4 == 0
417            && offset + 12 <= buf.size();
418        if self.ok(ok, "dispatch_indirect: pipeline, INDIRECT usage, offset or range").is_none() {
419            return;
420        }
421        // SAFETY: as above; range checked.
422        unsafe { (self.sh.f.cmd_dispatch_indirect)(self.cb, buf.handle(), offset) };
423    }
424
425    // ------------------------------------------------------------ render passes
426
427    pub fn begin_render(&mut self, desc: &RenderPassDesc<'_, 'a>) {
428        if self.outside("begin_render inside a render pass").is_none() {
429            return;
430        }
431        let n = desc.color.len();
432        if self.ok(n <= self.sh.limits.max_color_targets as usize && (n > 0 || desc.depth.is_some()),
433                   "begin_render: no attachments or too many").is_none()
434        {
435            return;
436        }
437        let first = desc.color.first().map(|c| c.view).or(desc.depth.map(|d| d.view));
438        let Some(first) = first else { return };
439        let (w, h) = first.size();
440        let single = |v: &TextureView| v.0.mips == 1 && v.0.layers == 1 && v.size() == (w, h);
441        let colors_ok = desc.color.iter().all(|c| {
442            single(c.view) && c.view.usage().contains(TextureUsage::COLOR_TARGET) && c.after != Layout::Undefined
443        });
444        let depth_ok = desc.depth.is_none_or(|d| {
445            single(d.view) && d.view.usage().contains(TextureUsage::DEPTH_TARGET) && d.after != Layout::Undefined
446        });
447        if self.ok(colors_ok && depth_ok, "begin_render: attachments (one mip and layer, equal sizes, usage)").is_none() {
448            return;
449        }
450        let mut barriers: Vec<vk::ImageMemoryBarrier> = Vec::with_capacity(n + 1);
451        let (mut ss, mut ds) = (0, 0);
452        let mut end = Vec::new();
453        let mut atts: Vec<vk::RenderingAttachmentInfo> = Vec::with_capacity(n);
454        let mut colors = [0u32; COLOR_TARGETS_MAX];
455        let load = |l: Load| match l {
456            Load::Keep => vk::ATTACHMENT_LOAD_OP_LOAD,
457            Load::Clear => vk::ATTACHMENT_LOAD_OP_CLEAR,
458            Load::Discard => vk::ATTACHMENT_LOAD_OP_DONT_CARE,
459        };
460        let store = |s: bool| if s { vk::ATTACHMENT_STORE_OP_STORE } else { vk::ATTACHMENT_STORE_OP_DONT_CARE };
461        for (i, c) in desc.color.iter().enumerate() {
462            let v = &c.view.0;
463            let range = vk::ImageSubresourceRange { aspect_mask: v.tex.aspect_all, base_mip_level: v.base_mip, level_count: 1,
464                                                    base_array_layer: v.base_layer, layer_count: 1 };
465            let (b, s, d) = image_barrier_info(v.tex.image, range, c.before, Layout::ColorTarget);
466            barriers.push(b);
467            ss |= s;
468            ds |= d;
469            if c.after != Layout::ColorTarget {
470                end.push(EndBarrier { image: v.tex.image, range, from: Layout::ColorTarget, to: c.after });
471            }
472            colors[i] = crate::vk_res::vk_format(c.view.format());
473            atts.push(vk::RenderingAttachmentInfo {
474                s_type: vk::STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
475                image_view: v.h,
476                image_layout: vk::IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
477                load_op: load(c.load),
478                store_op: store(c.store),
479                clear_value: c.clear.map(f32::to_bits),
480                ..vk::Zero::zero()
481            });
482        }
483        let mut datt: vk::RenderingAttachmentInfo = vk::Zero::zero();
484        let mut depth = vk::FORMAT_UNDEFINED;
485        if let Some(d) = desc.depth {
486            let v = &d.view.0;
487            let range = vk::ImageSubresourceRange { aspect_mask: v.tex.aspect_all, base_mip_level: v.base_mip, level_count: 1,
488                                                    base_array_layer: v.base_layer, layer_count: 1 };
489            let (b, s, dd) = image_barrier_info(v.tex.image, range, d.before, Layout::DepthTarget);
490            barriers.push(b);
491            ss |= s;
492            ds |= dd;
493            if d.after != Layout::DepthTarget {
494                end.push(EndBarrier { image: v.tex.image, range, from: Layout::DepthTarget, to: d.after });
495            }
496            depth = crate::vk_res::vk_format(d.view.format());
497            datt = vk::RenderingAttachmentInfo {
498                s_type: vk::STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
499                image_view: v.h,
500                image_layout: vk::IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
501                load_op: load(d.load),
502                store_op: store(d.store),
503                clear_value: [d.clear.to_bits(), 0, 0, 0],
504                ..vk::Zero::zero()
505            };
506        }
507        let has_stencil = desc.depth.is_some_and(|d| d.view.format().has_stencil());
508        let ri = vk::RenderingInfo {
509            s_type: vk::STRUCTURE_TYPE_RENDERING_INFO,
510            render_area: vk::Rect2D { offset: vk::Offset2D { x: 0, y: 0 }, extent: vk::Extent2D { width: w, height: h } },
511            layer_count: 1,
512            color_attachment_count: n as u32,
513            p_color_attachments: atts.as_ptr(),
514            p_depth_attachment: if desc.depth.is_some() { &datt } else { core::ptr::null() },
515            p_stencil_attachment: if has_stencil { &datt } else { core::ptr::null() },
516            ..vk::Zero::zero()
517        };
518        let vp = vk::Viewport { x: 0.0, y: h as f32, width: w as f32, height: -(h as f32), min_depth: 0.0, max_depth: 1.0 };
519        let sc = vk::Rect2D { offset: vk::Offset2D { x: 0, y: 0 }, extent: vk::Extent2D { width: w, height: h } };
520        // SAFETY: recording outside a pass; the attachments were checked; the structures live
521        // until the calls return.
522        unsafe {
523            (self.sh.f.cmd_pipeline_barrier)(self.cb, ss, ds, 0, 0, core::ptr::null(), 0, core::ptr::null(),
524                barriers.len() as u32, barriers.as_ptr());
525            (self.sh.f.cmd_begin_rendering)(self.cb, &ri);
526            (self.sh.f.cmd_set_viewport)(self.cb, 0, 1, &vp);
527            (self.sh.f.cmd_set_scissor)(self.cb, 0, 1, &sc);
528        }
529        self.pass = Some(Pass { colors, n, depth, end });
530        self.graphics = None;
531        self.index = false;
532    }
533
534    pub fn end_render(&mut self) {
535        let Some(pass) = self.pass.take() else {
536            self.ok(false, "end_render without begin_render");
537            return;
538        };
539        // SAFETY: recording inside the pass begun by begin_render.
540        unsafe { (self.sh.f.cmd_end_rendering)(self.cb) };
541        if pass.end.is_empty() {
542            return;
543        }
544        let (mut ss, mut ds) = (0, 0);
545        let barriers: Vec<vk::ImageMemoryBarrier> = pass
546            .end
547            .iter()
548            .map(|e| {
549                let (b, s, d) = image_barrier_info(e.image, e.range, e.from, e.to);
550                ss |= s;
551                ds |= d;
552                b
553            })
554            .collect();
555        // SAFETY: recording outside a pass.
556        unsafe {
557            (self.sh.f.cmd_pipeline_barrier)(self.cb, ss, ds, 0, 0, core::ptr::null(), 0, core::ptr::null(),
558                barriers.len() as u32, barriers.as_ptr())
559        };
560    }
561
562    pub fn set_render_pipeline(&mut self, p: &'a RenderPipeline) {
563        let fits = self.pass.as_ref().is_some_and(|pass| {
564            pass.n == p.0.ncolor && pass.colors[..pass.n] == p.0.colors[..pass.n] && pass.depth == p.0.depth
565        });
566        if self.ok(fits, "set_render_pipeline: outside a pass or target formats differ").is_none() {
567            return;
568        }
569        self.graphics = Some(&p.0);
570        // SAFETY: recording inside a pass.
571        unsafe { (self.sh.f.cmd_bind_pipeline)(self.cb, vk::PIPELINE_BIND_POINT_GRAPHICS, p.0.h) };
572    }
573
574    /// the viewport in pixels from the top left (depth range min ..= max)
575    pub fn set_viewport(&mut self, x: f32, y: f32, w: f32, h: f32, min_depth: f32, max_depth: f32) {
576        if self.ok(self.pass.is_some() && w > 0.0 && h > 0.0, "set_viewport: outside a pass or empty").is_none() {
577            return;
578        }
579        let vp = vk::Viewport { x, y: y + h, width: w, height: -h, min_depth, max_depth };
580        // SAFETY: recording inside a pass.
581        unsafe { (self.sh.f.cmd_set_viewport)(self.cb, 0, 1, &vp) };
582    }
583
584    pub fn set_scissor(&mut self, x: u32, y: u32, w: u32, h: u32) {
585        if self.ok(self.pass.is_some(), "set_scissor outside a pass").is_none() {
586            return;
587        }
588        let r = vk::Rect2D { offset: vk::Offset2D { x: x as i32, y: y as i32 }, extent: vk::Extent2D { width: w, height: h } };
589        // SAFETY: recording inside a pass.
590        unsafe { (self.sh.f.cmd_set_scissor)(self.cb, 0, 1, &r) };
591    }
592
593    pub fn set_vertex_buffer(&mut self, slot: u32, buf: &'a Buffer, offset: u64) {
594        let ok = self.pass.is_some() && buf.usage().contains(BufferUsage::VERTEX) && offset < buf.size()
595            && slot < self.sh.limits.max_vertex_buffers;
596        if self.ok(ok, "set_vertex_buffer: outside a pass, VERTEX usage or offset").is_none() {
597            return;
598        }
599        // SAFETY: recording inside a pass.
600        unsafe { (self.sh.f.cmd_bind_vertex_buffers)(self.cb, slot, 1, &buf.handle(), &offset) };
601    }
602
603    pub fn set_index_buffer(&mut self, buf: &'a Buffer, offset: u64, format: IndexFormat) {
604        let size = if format == IndexFormat::U16 { 2 } else { 4 };
605        let ok = self.pass.is_some() && buf.usage().contains(BufferUsage::INDEX) && offset % size == 0 && offset < buf.size();
606        if self.ok(ok, "set_index_buffer: outside a pass, INDEX usage or offset").is_none() {
607            return;
608        }
609        let t = if format == IndexFormat::U16 { vk::INDEX_TYPE_UINT16 } else { vk::INDEX_TYPE_UINT32 };
610        // SAFETY: recording inside a pass.
611        unsafe { (self.sh.f.cmd_bind_index_buffer)(self.cb, buf.handle(), offset, t) };
612        self.index = true;
613    }
614
615    #[inline]
616    fn can_draw(&mut self, indexed: bool) -> Option<()> {
617        let ok = self.pass.is_some() && self.graphics.is_some() && (!indexed || self.index);
618        self.ok(ok, "draw: outside a pass, without a render pipeline or index buffer")
619    }
620
621    pub fn draw(&mut self, vertices: u32, instances: u32, first_vertex: u32, first_instance: u32) {
622        if self.can_draw(false).is_some() {
623            // SAFETY: recording with a render pipeline inside a pass.
624            unsafe { (self.sh.f.cmd_draw)(self.cb, vertices, instances, first_vertex, first_instance) };
625        }
626    }
627
628    pub fn draw_indexed(&mut self, indices: u32, instances: u32, first_index: u32, base_vertex: i32, first_instance: u32) {
629        if self.can_draw(true).is_some() {
630            // SAFETY: as above, with an index buffer.
631            unsafe { (self.sh.f.cmd_draw_indexed)(self.cb, indices, instances, first_index, base_vertex, first_instance) };
632        }
633    }
634
635    fn indirect_ok(&mut self, buf: &Buffer, offset: u64, bytes: u64) -> Option<()> {
636        let ok = buf.usage().contains(BufferUsage::INDIRECT) && offset % 4 == 0 && offset.checked_add(bytes).is_some_and(|e| e <= buf.size());
637        self.ok(ok, "indirect: INDIRECT usage, offset (multiple of 4) or range")
638    }
639
640    /// one draw: vertex count, instance count, first vertex, first instance (4 x u32)
641    pub fn draw_indirect(&mut self, buf: &'a Buffer, offset: u64) {
642        if self.can_draw(false).is_some() && self.indirect_ok(buf, offset, 16).is_some() {
643            // SAFETY: recording; range checked.
644            unsafe { (self.sh.f.cmd_draw_indirect)(self.cb, buf.handle(), offset, 1, 16) };
645        }
646    }
647
648    /// one indexed draw: index count, instance count, first index, base vertex (i32), first
649    /// instance (5 x 32 bits)
650    pub fn draw_indexed_indirect(&mut self, buf: &'a Buffer, offset: u64) {
651        if self.can_draw(true).is_some() && self.indirect_ok(buf, offset, 20).is_some() {
652            // SAFETY: recording; range checked.
653            unsafe { (self.sh.f.cmd_draw_indexed_indirect)(self.cb, buf.handle(), offset, 1, 20) };
654        }
655    }
656
657    /// count indexed draws, stride bytes apart (Features::multi_draw_indirect)
658    pub fn draw_indexed_indirect_multi(&mut self, buf: &'a Buffer, offset: u64, count: u32, stride: u32) {
659        let ok = self.sh.features.multi_draw_indirect && stride >= 20 && stride % 4 == 0 && count > 0
660            && count <= self.sh.limits.max_draw_indirect_count;
661        if self.ok(ok, "draw_indexed_indirect_multi: needs multi_draw_indirect, stride >= 20").is_none()
662            || self.can_draw(true).is_none()
663            || self.indirect_ok(buf, offset, (count as u64 - 1) * stride as u64 + 20).is_none()
664        {
665            return;
666        }
667        // SAFETY: recording; range checked.
668        unsafe { (self.sh.f.cmd_draw_indexed_indirect)(self.cb, buf.handle(), offset, count, stride) };
669    }
670
671    /// up to max_count indexed draws, their number (u32) read from count_buf at count_offset
672    /// (level Voll: Features::draw_indirect_count)
673    pub fn draw_indexed_indirect_count(&mut self, buf: &'a Buffer, offset: u64, count_buf: &'a Buffer, count_offset: u64,
674                                       max_count: u32, stride: u32) {
675        let f = self.sh.f.cmd_draw_indexed_indirect_count;
676        let ok = self.sh.features.draw_indirect_count && f.is_some() && stride >= 20 && stride % 4 == 0 && max_count > 0;
677        if self.ok(ok, "draw_indexed_indirect_count: needs level Voll, stride >= 20").is_none()
678            || self.can_draw(true).is_none()
679            || self.indirect_ok(buf, offset, (max_count as u64 - 1) * stride as u64 + 20).is_none()
680            || self.indirect_ok(count_buf, count_offset, 4).is_none()
681        {
682            return;
683        }
684        let Some(f) = f else { return };
685        // SAFETY: recording; ranges checked; the feature is enabled.
686        unsafe { f(self.cb, buf.handle(), offset, count_buf.handle(), count_offset, max_count, stride) };
687    }
688
689    // ------------------------------------------------------------ timestamps
690
691    /// timestamps first .. first + n may be written again (outside render passes)
692    pub fn reset_timestamps(&mut self, ts: &'a Timestamps, first: u32, n: u32) {
693        let ok = self.pass.is_none() && n > 0 && first.checked_add(n).is_some_and(|e| e <= ts.0.count);
694        if self.ok(ok, "reset_timestamps: inside a pass or range").is_none() {
695            return;
696        }
697        // SAFETY: recording outside a pass; range checked.
698        unsafe { (self.sh.f.cmd_reset_query_pool)(self.cb, ts.0.pool, first, n) };
699    }
700
701    /// write timestamp i once every earlier command has finished (reset it first)
702    pub fn timestamp(&mut self, ts: &'a Timestamps, i: u32) {
703        if self.ok(i < ts.0.count, "timestamp: index outside the set").is_none() {
704            return;
705        }
706        // SAFETY: recording; index checked.
707        unsafe { (self.sh.f.cmd_write_timestamp)(self.cb, vk::PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, ts.0.pool, i) };
708    }
709
710    // ------------------------------------------------------------ end
711
712    /// the recorded commands, or the first misuse
713    pub fn finish(mut self) -> Result<CommandBuffer<'a>> {
714        if self.pass.is_some() {
715            self.ok(false, "finish inside a render pass");
716        }
717        if let Some(e) = self.err {
718            return Err(e); // Drop gives the slot back
719        }
720        let slot = self.slot.take().unwrap_or_else(|| unreachable!());
721        // SAFETY: the buffer is recording.
722        let r = unsafe { (self.sh.f.end_command_buffer)(slot.cb) };
723        if let Err(e) = self.sh.vk(r) {
724            self.sh.give_slot(slot);
725            return Err(e);
726        }
727        Ok(CommandBuffer { sh: self.sh, slot: Some(slot) })
728    }
729}