1use 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
44const LAYOUT_PRESENT: u32 = if cfg!(jfgui_glibc) { vk::IMAGE_LAYOUT_GENERAL } else { vk::IMAGE_LAYOUT_PRESENT_SRC_KHR };
47
48fn 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
67fn 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
107pub(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 unsafe { (f.cmd_pipeline_barrier)(cb, ss, ds, 0, 0, core::ptr::null(), 0, core::ptr::null(), 1, &b) };
116}
117
118const 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
125fn 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
159struct 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
174pub struct CommandEncoder<'a> {
176 sh: &'a Shared,
177 slot: Option<Slot>,
178 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
189pub 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 self.sh.give_slot(s);
215 }
216 }
217}
218
219impl Device {
220 pub fn encoder(&self) -> Result<CommandEncoder<'_>> {
222 CommandEncoder::of(&self.sh, false)
223 }
224}
225
226impl<'a> CommandEncoder<'a> {
227 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 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 #[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 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 unsafe { (self.sh.f.cmd_copy_buffer)(self.cb, src.handle(), dst.handle(), 1, &r) };
288 }
289
290 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 unsafe { (self.sh.f.cmd_fill_buffer)(self.cb, buf.handle(), offset, size, value) };
300 }
301
302 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 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 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 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 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 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 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 #[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 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 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 unsafe { (self.sh.f.cmd_bind_pipeline)(self.cb, vk::PIPELINE_BIND_POINT_COMPUTE, p.0.h) };
393 }
394
395 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 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 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 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 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 unsafe { (self.sh.f.cmd_dispatch)(self.cb, x, y, z) };
445 }
446
447 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 unsafe { (self.sh.f.cmd_dispatch_indirect)(self.cb, buf.handle(), offset) };
456 }
457
458 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 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 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 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 unsafe { (self.sh.f.cmd_bind_pipeline)(self.cb, vk::PIPELINE_BIND_POINT_GRAPHICS, p.0.h) };
607 }
608
609 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 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 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 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 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 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 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 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 unsafe { (self.sh.f.cmd_draw_indirect)(self.cb, buf.handle(), offset, 1, 16) };
680 }
681 }
682
683 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 unsafe { (self.sh.f.cmd_draw_indexed_indirect)(self.cb, buf.handle(), offset, 1, 20) };
689 }
690 }
691
692 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 unsafe { (self.sh.f.cmd_draw_indexed_indirect)(self.cb, buf.handle(), offset, count, stride) };
704 }
705
706 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 unsafe { f(self.cb, buf.handle(), offset, count_buf.handle(), count_offset, max_count, stride) };
722 }
723
724 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 unsafe { (self.sh.f.cmd_reset_query_pool)(self.cb, ts.0.pool, first, n) };
734 }
735
736 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 unsafe { (self.sh.f.cmd_write_timestamp)(self.cb, vk::PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, ts.0.pool, i) };
743 }
744
745 fn timestamps_here(&self) -> bool {
747 !self.compute || self.sh.lane.as_ref().is_some_and(|l| l.ts_bits > 0)
748 }
749
750 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); }
760 let slot = self.slot.take().unwrap_or_else(|| unreachable!());
761 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}