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