From bad11eef717a29ccd310a0ab97bfaa573cedb249 Mon Sep 17 00:00:00 2001 From: marc0246 <40955683+marc0246@users.noreply.github.com> Date: Fri, 20 Sep 2024 13:30:36 +0200 Subject: [PATCH] Task graph [5/10]: the new command buffer (#2567) --- Cargo.lock | 10 + examples/async-update/main.rs | 107 +- examples/bloom/Cargo.toml | 18 + examples/bloom/bloom.rs | 157 ++ examples/bloom/downsample.glsl | 97 ++ examples/bloom/main.rs | 598 +++++++ examples/bloom/scene.rs | 259 +++ examples/bloom/shared_exponent.glsl | 26 + examples/bloom/tonemap.glsl | 40 + examples/bloom/tonemap.rs | 212 +++ examples/bloom/upsample.glsl | 61 + .../src/command_buffer/commands/bind_push.rs | 278 +++ .../src/command_buffer/commands/clear.rs | 345 ++++ .../src/command_buffer/commands/copy.rs | 1531 +++++++++++++++++ .../command_buffer/commands/dynamic_state.rs | 781 +++++++++ .../src/command_buffer/commands/mod.rs | 6 + .../src/command_buffer/commands/pipeline.rs | 661 +++++++ .../src/command_buffer/commands/sync.rs | 474 +++++ vulkano-taskgraph/src/command_buffer/mod.rs | 110 ++ vulkano-taskgraph/src/graph/compile.rs | 112 +- vulkano-taskgraph/src/graph/execute.rs | 175 +- vulkano-taskgraph/src/graph/mod.rs | 16 +- vulkano-taskgraph/src/lib.rs | 97 +- vulkano-taskgraph/src/resource.rs | 27 +- vulkano/src/pipeline/layout.rs | 3 +- 25 files changed, 5933 insertions(+), 268 deletions(-) create mode 100644 examples/bloom/Cargo.toml create mode 100644 examples/bloom/bloom.rs create mode 100644 examples/bloom/downsample.glsl create mode 100644 examples/bloom/main.rs create mode 100644 examples/bloom/scene.rs create mode 100644 examples/bloom/shared_exponent.glsl create mode 100644 examples/bloom/tonemap.glsl create mode 100644 examples/bloom/tonemap.rs create mode 100644 examples/bloom/upsample.glsl create mode 100644 vulkano-taskgraph/src/command_buffer/commands/bind_push.rs create mode 100644 vulkano-taskgraph/src/command_buffer/commands/clear.rs create mode 100644 vulkano-taskgraph/src/command_buffer/commands/copy.rs create mode 100644 vulkano-taskgraph/src/command_buffer/commands/dynamic_state.rs create mode 100644 vulkano-taskgraph/src/command_buffer/commands/mod.rs create mode 100644 vulkano-taskgraph/src/command_buffer/commands/pipeline.rs create mode 100644 vulkano-taskgraph/src/command_buffer/commands/sync.rs create mode 100644 vulkano-taskgraph/src/command_buffer/mod.rs diff --git a/Cargo.lock b/Cargo.lock index b0cc2b6f69..d0c736084a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -208,6 +208,16 @@ dependencies = [ "objc2 0.5.2", ] +[[package]] +name = "bloom" +version = "0.0.0" +dependencies = [ + "vulkano", + "vulkano-shaders", + "vulkano-taskgraph", + "winit 0.29.15", +] + [[package]] name = "buffer-allocator" version = "0.0.0" diff --git a/examples/async-update/main.rs b/examples/async-update/main.rs index 4f1808674e..0406ab548f 100644 --- a/examples/async-update/main.rs +++ b/examples/async-update/main.rs @@ -41,11 +41,8 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; use vulkano::{ - buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}, - command_buffer::{ - sys::RawRecordingCommandBuffer, BufferImageCopy, ClearColorImageInfo, - CopyBufferToImageInfo, RenderPassBeginInfo, - }, + buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage}, + command_buffer::RenderPassBeginInfo, descriptor_set::{ allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet, }, @@ -57,7 +54,7 @@ use vulkano::{ image::{ sampler::{Sampler, SamplerCreateInfo}, view::ImageView, - Image, ImageCreateInfo, ImageType, ImageUsage, + Image, ImageAspects, ImageCreateInfo, ImageSubresourceLayers, ImageType, ImageUsage, }, instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}, memory::allocator::{AllocationCreateInfo, DeviceLayout, MemoryTypeFilter}, @@ -81,6 +78,9 @@ use vulkano::{ DeviceSize, Validated, VulkanError, VulkanLibrary, }; use vulkano_taskgraph::{ + command_buffer::{ + BufferImageCopy, ClearColorImageInfo, CopyBufferToImageInfo, RecordingCommandBuffer, + }, graph::{CompileInfo, ExecuteError, TaskGraph}, resource::{AccessType, Flight, HostAccessType, ImageLayoutType, Resources}, resource_map, Id, QueueFamilyType, Task, TaskContext, TaskResult, @@ -224,7 +224,7 @@ fn main() -> Result<(), impl Error> { {transfer_family_index} for transfers", ); - let resources = Resources::new(device.clone(), Default::default()); + let resources = Resources::new(&device, &Default::default()); let graphics_flight_id = resources.create_flight(MAX_FRAMES_IN_FLIGHT).unwrap(); let transfer_flight_id = resources.create_flight(1).unwrap(); @@ -343,16 +343,18 @@ fn main() -> Result<(), impl Error> { // Initialize the resources. unsafe { vulkano_taskgraph::execute( - graphics_queue.clone(), - resources.clone(), + &graphics_queue, + &resources, graphics_flight_id, |cbf, tcx| { tcx.write_buffer::<[MyVertex]>(vertex_buffer_id, ..)? .copy_from_slice(&vertices); for &texture_id in &texture_ids { - let texture = tcx.image(texture_id)?.image(); - cbf.clear_color_image(&ClearColorImageInfo::image(texture.clone()))?; + cbf.clear_color_image(&ClearColorImageInfo { + image: texture_id, + ..Default::default() + })?; } Ok(()) @@ -504,7 +506,7 @@ fn main() -> Result<(), impl Error> { framebuffers, }; - let mut task_graph = TaskGraph::new(resources.clone(), 1, 4); + let mut task_graph = TaskGraph::new(&resources, 1, 4); let virtual_swapchain_id = task_graph.add_swapchain(&SwapchainCreateInfo::default()); let virtual_texture_id = task_graph.add_image(&texture_create_info); @@ -543,9 +545,9 @@ fn main() -> Result<(), impl Error> { ); let task_graph = unsafe { - task_graph.compile(CompileInfo { - queues: vec![graphics_queue.clone()], - present_queue: Some(graphics_queue.clone()), + task_graph.compile(&CompileInfo { + queues: &[&graphics_queue], + present_queue: Some(&graphics_queue), flight_id: graphics_flight_id, ..Default::default() }) @@ -652,7 +654,6 @@ fn main() -> Result<(), impl Error> { } Event::LoopExiting => { let flight = resources.flight(graphics_flight_id).unwrap(); - flight.destroy_object(pipeline.clone()); flight.destroy_objects(rcx.framebuffers.drain(..)); flight.destroy_objects(uniform_buffer_sets.clone()); flight.destroy_objects(sampler_sets.clone()); @@ -729,14 +730,13 @@ impl Task for RenderTask { unsafe fn execute( &self, - cbf: &mut RawRecordingCommandBuffer, + cbf: &mut RecordingCommandBuffer<'_>, tcx: &mut TaskContext<'_>, rcx: &Self::World, ) -> TaskResult { let frame_index = tcx.current_frame_index(); let swapchain_state = tcx.swapchain(self.swapchain_id)?; let image_index = swapchain_state.current_image_index().unwrap(); - let vertex_buffer = Subbuffer::from(tcx.buffer(self.vertex_buffer_id)?.buffer().clone()); // Write to the uniform buffer designated for this frame. *tcx.write_buffer(self.uniform_buffer_id, ..)? = vs::Data { @@ -755,16 +755,16 @@ impl Task for RenderTask { }, }; - cbf.begin_render_pass( + cbf.as_raw().begin_render_pass( &RenderPassBeginInfo { clear_values: vec![Some([0.0, 0.0, 0.0, 1.0].into())], ..RenderPassBeginInfo::framebuffer(rcx.framebuffers[image_index as usize].clone()) }, &Default::default(), - )? - .set_viewport(0, slice::from_ref(&rcx.viewport))? - .bind_pipeline_graphics(&self.pipeline)? - .bind_descriptor_sets( + )?; + cbf.set_viewport(0, slice::from_ref(&rcx.viewport))?; + cbf.bind_pipeline_graphics(&self.pipeline)?; + cbf.as_raw().bind_descriptor_sets( PipelineBindPoint::Graphics, self.pipeline.layout(), 0, @@ -778,13 +778,12 @@ impl Task for RenderTask { .clone() .into(), ], - )? - .bind_vertex_buffers(0, slice::from_ref(&vertex_buffer))?; + )?; + cbf.bind_vertex_buffers(0, &[self.vertex_buffer_id], &[0], &[], &[])?; - let vertex_count = vertex_buffer.reinterpret_ref::<[MyVertex]>().len(); - unsafe { cbf.draw(vertex_count as u32, 1, 0, 0) }?; + unsafe { cbf.draw(4, 1, 0, 0) }?; - cbf.end_render_pass(&Default::default())?; + cbf.as_raw().end_render_pass(&Default::default())?; Ok(()) } @@ -831,7 +830,7 @@ fn run_worker( .unwrap() }); - let mut task_graph = TaskGraph::new(resources.clone(), 1, 3); + let mut task_graph = TaskGraph::new(&resources, 1, 3); let virtual_front_staging_buffer_id = task_graph.add_buffer(&BufferCreateInfo::default()); let virtual_back_staging_buffer_id = task_graph.add_buffer(&BufferCreateInfo::default()); @@ -861,8 +860,8 @@ fn run_worker( ); let task_graph = unsafe { - task_graph.compile(CompileInfo { - queues: vec![transfer_queue], + task_graph.compile(&CompileInfo { + queues: &[&transfer_queue], flight_id: transfer_flight_id, ..Default::default() }) @@ -942,7 +941,7 @@ impl Task for UploadTask { unsafe fn execute( &self, - cbf: &mut RawRecordingCommandBuffer, + cbf: &mut RecordingCommandBuffer<'_>, tcx: &mut TaskContext<'_>, ¤t_corner: &Self::World, ) -> TaskResult { @@ -967,41 +966,37 @@ impl Task for UploadTask { tcx.write_buffer::<[_]>(self.front_staging_buffer_id, ..)? .fill(color); - let texture = tcx.image(self.texture_id)?.image(); - cbf.copy_buffer_to_image(&CopyBufferToImageInfo { - regions: [BufferImageCopy { - image_subresource: texture.subresource_layers(), + src_buffer: self.front_staging_buffer_id, + dst_image: self.texture_id, + regions: &[BufferImageCopy { + image_subresource: ImageSubresourceLayers { + aspects: ImageAspects::COLOR, + mip_level: 0, + array_layers: 0..1, + }, image_offset: CORNER_OFFSETS[current_corner % 4], image_extent: [TRANSFER_GRANULARITY, TRANSFER_GRANULARITY, 1], ..Default::default() - }] - .into(), - ..CopyBufferToImageInfo::buffer_image( - tcx.buffer(self.front_staging_buffer_id)? - .buffer() - .clone() - .into(), - texture.clone(), - ) + }], + ..Default::default() })?; if current_corner > 0 { cbf.copy_buffer_to_image(&CopyBufferToImageInfo { - regions: [BufferImageCopy { - image_subresource: texture.subresource_layers(), + src_buffer: self.back_staging_buffer_id, + dst_image: self.texture_id, + regions: &[BufferImageCopy { + image_subresource: ImageSubresourceLayers { + aspects: ImageAspects::COLOR, + mip_level: 0, + array_layers: 0..1, + }, image_offset: CORNER_OFFSETS[(current_corner - 1) % 4], image_extent: [TRANSFER_GRANULARITY, TRANSFER_GRANULARITY, 1], ..Default::default() - }] - .into(), - ..CopyBufferToImageInfo::buffer_image( - tcx.buffer(self.back_staging_buffer_id)? - .buffer() - .clone() - .into(), - texture.clone(), - ) + }], + ..Default::default() })?; } diff --git a/examples/bloom/Cargo.toml b/examples/bloom/Cargo.toml new file mode 100644 index 0000000000..b48dcdf455 --- /dev/null +++ b/examples/bloom/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "bloom" +version = "0.0.0" +edition = "2021" +publish = false + +[[bin]] +name = "bloom" +path = "main.rs" +test = false +bench = false +doc = false + +[dependencies] +vulkano = { workspace = true, default-features = true } +vulkano-shaders = { workspace = true } +vulkano-taskgraph = { workspace = true } +winit = { workspace = true, default-features = true } diff --git a/examples/bloom/bloom.rs b/examples/bloom/bloom.rs new file mode 100644 index 0000000000..1c0bd9ba09 --- /dev/null +++ b/examples/bloom/bloom.rs @@ -0,0 +1,157 @@ +use crate::RenderContext; +use std::{slice, sync::Arc}; +use vulkano::{ + image::{mip_level_extent, Image}, + pipeline::{ + compute::ComputePipelineCreateInfo, ComputePipeline, PipelineBindPoint, + PipelineShaderStageCreateInfo, + }, + sync::{AccessFlags, PipelineStages}, +}; +use vulkano_taskgraph::{ + command_buffer::{DependencyInfo, MemoryBarrier, RecordingCommandBuffer}, + Id, Task, TaskContext, TaskResult, +}; + +const THRESHOLD: f32 = 1.5; +const KNEE: f32 = 0.1; +const INTENSITY: f32 = 1.0; + +pub struct BloomTask { + downsample_pipeline: Arc, + upsample_pipeline: Arc, + bloom_image_id: Id, +} + +impl BloomTask { + pub fn new(rcx: &RenderContext, bloom_image_id: Id) -> Self { + let downsample_pipeline = { + let cs = downsample::load(rcx.device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + let stage = PipelineShaderStageCreateInfo::new(cs); + + ComputePipeline::new( + rcx.device.clone(), + None, + ComputePipelineCreateInfo::stage_layout(stage, rcx.pipeline_layout.clone()), + ) + .unwrap() + }; + + let upsample_pipeline = { + let cs = upsample::load(rcx.device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + let stage = PipelineShaderStageCreateInfo::new(cs); + + ComputePipeline::new( + rcx.device.clone(), + None, + ComputePipelineCreateInfo::stage_layout(stage, rcx.pipeline_layout.clone()), + ) + .unwrap() + }; + + BloomTask { + downsample_pipeline, + upsample_pipeline, + bloom_image_id, + } + } +} + +impl Task for BloomTask { + type World = RenderContext; + + unsafe fn execute( + &self, + cbf: &mut RecordingCommandBuffer<'_>, + tcx: &mut TaskContext<'_>, + rcx: &Self::World, + ) -> TaskResult { + cbf.as_raw().bind_descriptor_sets( + PipelineBindPoint::Compute, + &rcx.pipeline_layout, + 0, + slice::from_ref(&rcx.descriptor_set), + )?; + + let bloom_image = tcx.image(self.bloom_image_id)?.image(); + + let dependency_info = DependencyInfo { + memory_barriers: &[MemoryBarrier { + src_stages: PipelineStages::COMPUTE_SHADER, + src_access: AccessFlags::SHADER_WRITE, + dst_stages: PipelineStages::COMPUTE_SHADER, + dst_access: AccessFlags::SHADER_READ, + ..Default::default() + }], + ..Default::default() + }; + + cbf.bind_pipeline_compute(&self.downsample_pipeline)?; + + for src_mip_level in 0..bloom_image.mip_levels() - 1 { + let dst_mip_level = src_mip_level + 1; + let dst_extent = mip_level_extent(bloom_image.extent(), dst_mip_level).unwrap(); + let group_counts = dst_extent.map(|c| (c + 7) / 8); + + cbf.push_constants( + &rcx.pipeline_layout, + 0, + &downsample::PushConstants { + dst_mip_level, + threshold: THRESHOLD, + knee: KNEE, + }, + )?; + + unsafe { cbf.dispatch(group_counts) }?; + + cbf.pipeline_barrier(&dependency_info)?; + } + + cbf.bind_pipeline_compute(&self.upsample_pipeline)?; + + for dst_mip_level in (0..bloom_image.mip_levels() - 1).rev() { + let dst_extent = mip_level_extent(bloom_image.extent(), dst_mip_level).unwrap(); + let group_counts = dst_extent.map(|c| (c + 7) / 8); + + cbf.push_constants( + &rcx.pipeline_layout, + 0, + &upsample::PushConstants { + dst_mip_level, + intensity: INTENSITY, + }, + )?; + + unsafe { cbf.dispatch(group_counts) }?; + + if dst_mip_level != 0 { + cbf.pipeline_barrier(&dependency_info)?; + } + } + + Ok(()) + } +} + +mod downsample { + vulkano_shaders::shader! { + ty: "compute", + path: "downsample.glsl", + include: ["."], + } +} + +mod upsample { + vulkano_shaders::shader! { + ty: "compute", + path: "upsample.glsl", + include: ["."], + } +} diff --git a/examples/bloom/downsample.glsl b/examples/bloom/downsample.glsl new file mode 100644 index 0000000000..99fb723a50 --- /dev/null +++ b/examples/bloom/downsample.glsl @@ -0,0 +1,97 @@ +#version 450 +#include + +const uint MAX_BLOOM_MIP_LEVELS = 6; +const float EPSILON = 1.19209290e-07; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0) uniform sampler bloom_sampler; +layout(set = 0, binding = 1) uniform texture2D bloom_texture; +layout(set = 0, binding = 2, r32ui) uniform uimage2D bloom_mip_chain[MAX_BLOOM_MIP_LEVELS]; + +layout(push_constant) uniform PushConstants { + uint dst_mip_level; + float threshold; + float knee; +}; + +uint src_mip_level = dst_mip_level - 1; + +vec3 quadraticThreshold(vec3 color) { + float brightness = max(color.r, max(color.g, color.b)); + float quadratic_response = clamp(brightness - threshold + knee, 0.0, 2.0 * knee); + quadratic_response = (quadratic_response * quadratic_response) / (0.25 / knee); + + color *= max(quadratic_response, brightness - threshold) / max(brightness, EPSILON); + + return color; +} + +vec3 prefilter(vec3 color) { + return quadraticThreshold(color); +} + +vec3 sample1(vec2 uv) { + return textureLod(sampler2D(bloom_texture, bloom_sampler), uv, src_mip_level).rgb; +} + +// 13-tap box filter. +// ┌───┬───┬───┐ +// │ A │ B │ C │ +// ├──╴D╶─╴E╶──┤ +// │ F │ G │ H │ +// ├──╴I╶─╴J╶──┤ +// │ K │ L │ M │ +// └───┴───┴───┘ +vec3 downsampleBox13(vec2 uv, vec2 src_texel_size) { + vec3 a = sample1(uv + vec2(-2.0, -2.0) * src_texel_size); + vec3 b = sample1(uv + vec2( 0.0, -2.0) * src_texel_size); + vec3 c = sample1(uv + vec2( 2.0, -2.0) * src_texel_size); + vec3 d = sample1(uv + vec2(-1.0, -1.0) * src_texel_size); + vec3 e = sample1(uv + vec2( 1.0, -1.0) * src_texel_size); + vec3 f = sample1(uv + vec2(-2.0, 0.0) * src_texel_size); + vec3 g = sample1(uv + vec2( 0.0, 0.0) * src_texel_size); + vec3 h = sample1(uv + vec2( 2.0, 0.0) * src_texel_size); + vec3 i = sample1(uv + vec2(-1.0, 1.0) * src_texel_size); + vec3 j = sample1(uv + vec2( 1.0, 1.0) * src_texel_size); + vec3 k = sample1(uv + vec2(-2.0, 2.0) * src_texel_size); + vec3 l = sample1(uv + vec2( 0.0, 2.0) * src_texel_size); + vec3 m = sample1(uv + vec2( 2.0, 2.0) * src_texel_size); + + vec3 color; + color = (d + e + i + j) * 0.25 * 0.5; + color += (a + b + f + g) * 0.25 * 0.125; + color += (b + c + g + h) * 0.25 * 0.125; + color += (f + g + k + l) * 0.25 * 0.125; + color += (g + h + l + m) * 0.25 * 0.125; + + return color; +} + +void store(ivec2 dst_coord, vec3 color) { + uint packed = convertToSharedExponent(color); + imageStore(bloom_mip_chain[dst_mip_level], dst_coord, uvec4(packed, 0, 0, 0)); +} + +void main() { + ivec2 dst_coord = ivec2(gl_GlobalInvocationID.xy); + ivec2 dst_size = imageSize(bloom_mip_chain[dst_mip_level]); + + if (dst_coord.x > dst_size.x || dst_coord.y > dst_size.y) { + return; + } + + ivec2 src_size = textureSize(sampler2D(bloom_texture, bloom_sampler), int(src_mip_level)); + vec2 src_texel_size = 1.0 / vec2(src_size); + vec2 uv = (vec2(dst_coord) + 0.5) / vec2(dst_size); + vec3 color = downsampleBox13(uv, src_texel_size); + + if (src_mip_level == 0) { + color = prefilter(color); + } + + color = max(color, 0.0001); + + store(dst_coord, color); +} diff --git a/examples/bloom/main.rs b/examples/bloom/main.rs new file mode 100644 index 0000000000..cba71d6b3e --- /dev/null +++ b/examples/bloom/main.rs @@ -0,0 +1,598 @@ +// TODO: document + +use bloom::BloomTask; +use scene::SceneTask; +use std::{cmp, error::Error, sync::Arc}; +use tonemap::TonemapTask; +use vulkano::{ + descriptor_set::{ + allocator::StandardDescriptorSetAllocator, + layout::{ + DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, + DescriptorType, + }, + DescriptorImageViewInfo, DescriptorSet, DescriptorSetWithOffsets, WriteDescriptorSet, + }, + device::{ + physical::PhysicalDeviceType, Device, DeviceCreateInfo, DeviceExtensions, DeviceOwned, + Queue, QueueCreateInfo, QueueFlags, + }, + format::{Format, NumericFormat}, + image::{ + max_mip_levels, + sampler::{Filter, Sampler, SamplerCreateInfo, SamplerMipmapMode, LOD_CLAMP_NONE}, + view::{ImageView, ImageViewCreateInfo}, + Image, ImageAspects, ImageCreateFlags, ImageCreateInfo, ImageLayout, ImageSubresourceRange, + ImageType, ImageUsage, + }, + instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}, + memory::allocator::AllocationCreateInfo, + pipeline::{ + graphics::viewport::Viewport, + layout::{PipelineLayoutCreateInfo, PushConstantRange}, + PipelineLayout, + }, + shader::ShaderStages, + swapchain::{ColorSpace, Surface, Swapchain, SwapchainCreateInfo}, + Validated, Version, VulkanError, VulkanLibrary, +}; +use vulkano_taskgraph::{ + graph::{CompileInfo, ExecuteError, TaskGraph}, + resource::{AccessType, Flight, ImageLayoutType, Resources}, + resource_map, Id, QueueFamilyType, +}; +use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::{Window, WindowBuilder}, +}; + +mod bloom; +mod scene; +mod tonemap; + +const MAX_FRAMES_IN_FLIGHT: u32 = 2; +const MAX_BLOOM_MIP_LEVELS: u32 = 6; + +fn main() -> Result<(), impl Error> { + let event_loop = EventLoop::new().unwrap(); + + let library = VulkanLibrary::new().unwrap(); + let required_extensions = Surface::required_extensions(&event_loop).unwrap(); + let instance = Instance::new( + library, + InstanceCreateInfo { + flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, + enabled_extensions: required_extensions, + ..Default::default() + }, + ) + .unwrap(); + + let mut rcx = RenderContext::new(&event_loop, &instance); + + let mut task_graph = TaskGraph::new(&rcx.resources, 3, 2); + + let virtual_swapchain_id = task_graph.add_swapchain(&SwapchainCreateInfo::default()); + let virtual_bloom_image_id = task_graph.add_image(&ImageCreateInfo::default()); + + let scene_node_id = task_graph + .create_task_node("Scene", QueueFamilyType::Graphics, SceneTask::new(&rcx)) + .image_access( + virtual_bloom_image_id, + AccessType::ColorAttachmentWrite, + ImageLayoutType::Optimal, + ) + .build(); + let bloom_node_id = task_graph + .create_task_node( + "Bloom", + QueueFamilyType::Compute, + BloomTask::new(&rcx, virtual_bloom_image_id), + ) + .image_access( + virtual_bloom_image_id, + AccessType::ComputeShaderSampledRead, + ImageLayoutType::General, + ) + .image_access( + virtual_bloom_image_id, + AccessType::ComputeShaderStorageWrite, + ImageLayoutType::General, + ) + .build(); + let tonemap_node_id = task_graph + .create_task_node( + "Tonemap", + QueueFamilyType::Graphics, + TonemapTask::new(&rcx, virtual_swapchain_id), + ) + .image_access( + virtual_swapchain_id.current_image_id(), + AccessType::ColorAttachmentWrite, + ImageLayoutType::Optimal, + ) + .image_access( + virtual_bloom_image_id, + AccessType::FragmentShaderSampledRead, + ImageLayoutType::General, + ) + .build(); + + task_graph.add_edge(scene_node_id, bloom_node_id).unwrap(); + task_graph.add_edge(bloom_node_id, tonemap_node_id).unwrap(); + + let mut task_graph = unsafe { + task_graph.compile(&CompileInfo { + queues: &[&rcx.queue], + present_queue: Some(&rcx.queue), + flight_id: rcx.flight_id, + ..Default::default() + }) + } + .unwrap(); + + let mut recreate_swapchain = false; + + event_loop.run(move |event, elwt| { + elwt.set_control_flow(ControlFlow::Poll); + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + elwt.exit(); + } + Event::WindowEvent { + event: WindowEvent::Resized(_), + .. + } => { + recreate_swapchain = true; + } + Event::WindowEvent { + event: WindowEvent::RedrawRequested, + .. + } => { + let image_extent: [u32; 2] = rcx.window.inner_size().into(); + + if image_extent.contains(&0) { + return; + } + + if recreate_swapchain { + rcx.handle_resize(); + + task_graph + .task_node_mut(scene_node_id) + .unwrap() + .task_mut() + .downcast_mut::() + .unwrap() + .handle_resize(&rcx); + task_graph + .task_node_mut(tonemap_node_id) + .unwrap() + .task_mut() + .downcast_mut::() + .unwrap() + .handle_resize(&rcx); + + recreate_swapchain = false; + } + + let flight = rcx.resources.flight(rcx.flight_id).unwrap(); + + flight.wait(None).unwrap(); + + let resource_map = resource_map!( + &task_graph, + virtual_swapchain_id => rcx.swapchain_id, + virtual_bloom_image_id => rcx.bloom_image_id, + ) + .unwrap(); + + match unsafe { + task_graph.execute(resource_map, &rcx, || rcx.window.pre_present_notify()) + } { + Ok(()) => {} + Err(ExecuteError::Swapchain { + error: Validated::Error(VulkanError::OutOfDate), + .. + }) => { + recreate_swapchain = true; + } + Err(e) => { + panic!("failed to execute next frame: {e:?}"); + } + } + } + Event::AboutToWait => { + rcx.window.request_redraw(); + } + Event::LoopExiting => { + rcx.cleanup(); + + task_graph + .task_node_mut(scene_node_id) + .unwrap() + .task_mut() + .downcast_mut::() + .unwrap() + .cleanup(&rcx); + task_graph + .task_node_mut(tonemap_node_id) + .unwrap() + .task_mut() + .downcast_mut::() + .unwrap() + .cleanup(&rcx); + } + _ => (), + } + }) +} + +pub struct RenderContext { + device: Arc, + queue: Arc, + resources: Arc, + flight_id: Id, + window: Arc, + swapchain_id: Id, + swapchain_format: Format, + bloom_image_id: Id, + viewport: Viewport, + pipeline_layout: Arc, + descriptor_set_allocator: Arc, + sampler: Arc, + descriptor_set: DescriptorSetWithOffsets, +} + +impl RenderContext { + fn new(event_loop: &EventLoop<()>, instance: &Arc) -> Self { + let mut device_extensions = DeviceExtensions { + khr_swapchain: true, + ..DeviceExtensions::empty() + }; + let (physical_device, queue_family_index) = instance + .enumerate_physical_devices() + .unwrap() + .filter(|p| { + p.api_version() >= Version::V1_1 || p.supported_extensions().khr_maintenance2 + }) + .filter(|p| p.supported_extensions().contains(&device_extensions)) + .filter_map(|p| { + p.queue_family_properties() + .iter() + .enumerate() + .position(|(i, q)| { + q.queue_flags + .contains(QueueFlags::GRAPHICS | QueueFlags::COMPUTE) + && p.presentation_support(i as u32, event_loop).unwrap() + }) + .map(|i| (p, i as u32)) + }) + .min_by_key(|(p, _)| match p.properties().device_type { + PhysicalDeviceType::DiscreteGpu => 0, + PhysicalDeviceType::IntegratedGpu => 1, + PhysicalDeviceType::VirtualGpu => 2, + PhysicalDeviceType::Cpu => 3, + PhysicalDeviceType::Other => 4, + _ => 5, + }) + .unwrap(); + + println!( + "Using device: {} (type: {:?})", + physical_device.properties().device_name, + physical_device.properties().device_type, + ); + + if physical_device.api_version() < Version::V1_1 { + device_extensions.khr_maintenance2 = true; + } + + if physical_device.api_version() < Version::V1_2 + && physical_device.supported_extensions().khr_image_format_list + { + device_extensions.khr_image_format_list = true; + } + + let (device, mut queues) = Device::new( + physical_device, + DeviceCreateInfo { + enabled_extensions: device_extensions, + queue_create_infos: vec![QueueCreateInfo { + queue_family_index, + ..Default::default() + }], + ..Default::default() + }, + ) + .unwrap(); + + let queue = queues.next().unwrap(); + + let resources = Resources::new(&device, &Default::default()); + + let flight_id = resources.create_flight(MAX_FRAMES_IN_FLIGHT).unwrap(); + + let window = Arc::new(WindowBuilder::new().build(event_loop).unwrap()); + let surface = Surface::from_window(instance.clone(), window.clone()).unwrap(); + + let swapchain_format; + let swapchain_id = { + let surface_capabilities = device + .physical_device() + .surface_capabilities(&surface, Default::default()) + .unwrap(); + (swapchain_format, _) = device + .physical_device() + .surface_formats(&surface, Default::default()) + .unwrap() + .into_iter() + .find(|&(format, color_space)| { + format.numeric_format_color() == Some(NumericFormat::SRGB) + && color_space == ColorSpace::SrgbNonLinear + }) + .unwrap(); + + resources + .create_swapchain( + flight_id, + surface, + SwapchainCreateInfo { + min_image_count: surface_capabilities.min_image_count.max(3), + image_format: swapchain_format, + image_extent: window.inner_size().into(), + image_usage: ImageUsage::COLOR_ATTACHMENT, + composite_alpha: surface_capabilities + .supported_composite_alpha + .into_iter() + .next() + .unwrap(), + ..Default::default() + }, + ) + .unwrap() + }; + + let viewport = Viewport { + offset: [0.0, 0.0], + extent: window.inner_size().into(), + depth_range: 0.0..=1.0, + }; + + let pipeline_layout = PipelineLayout::new( + device.clone(), + PipelineLayoutCreateInfo { + set_layouts: vec![DescriptorSetLayout::new( + device.clone(), + DescriptorSetLayoutCreateInfo { + bindings: [ + ( + 0, + DescriptorSetLayoutBinding { + stages: ShaderStages::FRAGMENT | ShaderStages::COMPUTE, + ..DescriptorSetLayoutBinding::descriptor_type( + DescriptorType::Sampler, + ) + }, + ), + ( + 1, + DescriptorSetLayoutBinding { + stages: ShaderStages::FRAGMENT | ShaderStages::COMPUTE, + ..DescriptorSetLayoutBinding::descriptor_type( + DescriptorType::SampledImage, + ) + }, + ), + ( + 2, + DescriptorSetLayoutBinding { + stages: ShaderStages::COMPUTE, + descriptor_count: MAX_BLOOM_MIP_LEVELS, + ..DescriptorSetLayoutBinding::descriptor_type( + DescriptorType::StorageImage, + ) + }, + ), + ] + .into_iter() + .collect(), + ..Default::default() + }, + ) + .unwrap()], + push_constant_ranges: vec![PushConstantRange { + stages: ShaderStages::FRAGMENT | ShaderStages::COMPUTE, + offset: 0, + size: 12, + }], + ..Default::default() + }, + ) + .unwrap(); + + let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new( + device.clone(), + Default::default(), + )); + + let sampler = Sampler::new( + device.clone(), + SamplerCreateInfo { + mag_filter: Filter::Linear, + min_filter: Filter::Linear, + mipmap_mode: SamplerMipmapMode::Nearest, + lod: 0.0..=LOD_CLAMP_NONE, + ..Default::default() + }, + ) + .unwrap(); + + let (bloom_image_id, descriptor_set) = window_size_dependent_setup( + &resources, + swapchain_id, + &pipeline_layout, + &sampler, + &descriptor_set_allocator, + ); + + RenderContext { + device, + queue, + window, + resources, + flight_id, + swapchain_id, + swapchain_format, + bloom_image_id, + viewport, + pipeline_layout, + sampler, + descriptor_set_allocator, + descriptor_set, + } + } + + fn handle_resize(&mut self) { + let window_size = self.window.inner_size(); + + self.swapchain_id = self + .resources + .recreate_swapchain(self.swapchain_id, |create_info| SwapchainCreateInfo { + image_extent: window_size.into(), + ..create_info + }) + .expect("failed to recreate swapchain"); + + let flight = self.resources.flight(self.flight_id).unwrap(); + let bloom_image_state = + unsafe { self.resources.remove_image(self.bloom_image_id) }.unwrap(); + flight.destroy_object(bloom_image_state.image().clone()); + flight.destroy_object(self.descriptor_set.as_ref().0.clone()); + + (self.bloom_image_id, self.descriptor_set) = window_size_dependent_setup( + &self.resources, + self.swapchain_id, + &self.pipeline_layout, + &self.sampler, + &self.descriptor_set_allocator, + ); + + self.viewport.extent = window_size.into(); + } + + fn cleanup(&mut self) { + let flight = self.resources.flight(self.flight_id).unwrap(); + flight.destroy_object(self.descriptor_set.as_ref().0.clone()); + } +} + +/// This function is called once during initialization, then again whenever the window is resized. +fn window_size_dependent_setup( + resources: &Resources, + swapchain_id: Id, + pipeline_layout: &Arc, + sampler: &Arc, + descriptor_set_allocator: &Arc, +) -> (Id, DescriptorSetWithOffsets) { + let device = resources.device(); + let swapchain_state = resources.swapchain(swapchain_id).unwrap(); + let images = swapchain_state.images(); + let extent = images[0].extent(); + + let bloom_image_mip_levels = cmp::min(MAX_BLOOM_MIP_LEVELS, max_mip_levels(extent)); + + let bloom_image_id = { + let view_formats = if device.api_version() >= Version::V1_2 + || device.enabled_extensions().khr_image_format_list + { + vec![Format::R32_UINT, Format::E5B9G9R9_UFLOAT_PACK32] + } else { + Vec::new() + }; + + resources + .create_image( + ImageCreateInfo { + flags: ImageCreateFlags::MUTABLE_FORMAT, + image_type: ImageType::Dim2d, + format: Format::R32_UINT, + view_formats, + extent, + mip_levels: bloom_image_mip_levels, + usage: ImageUsage::TRANSFER_DST + | ImageUsage::SAMPLED + | ImageUsage::STORAGE + | ImageUsage::COLOR_ATTACHMENT, + ..Default::default() + }, + AllocationCreateInfo::default(), + ) + .unwrap() + }; + + let bloom_image_state = resources.image(bloom_image_id).unwrap(); + let bloom_image = bloom_image_state.image(); + + let bloom_texture_view = ImageView::new( + bloom_image.clone(), + ImageViewCreateInfo { + format: Format::E5B9G9R9_UFLOAT_PACK32, + subresource_range: bloom_image.subresource_range(), + usage: ImageUsage::SAMPLED, + ..Default::default() + }, + ) + .unwrap(); + + let bloom_mip_chain_views = (0..MAX_BLOOM_MIP_LEVELS).map(|mip_level| { + let mip_level = cmp::min(mip_level, max_mip_levels(extent) - 1); + + ImageView::new( + bloom_image.clone(), + ImageViewCreateInfo { + format: Format::R32_UINT, + subresource_range: ImageSubresourceRange { + aspects: ImageAspects::COLOR, + mip_levels: mip_level..mip_level + 1, + array_layers: 0..1, + }, + usage: ImageUsage::STORAGE, + ..Default::default() + }, + ) + .unwrap() + }); + + let descriptor_set = DescriptorSet::new( + descriptor_set_allocator.clone(), + pipeline_layout.set_layouts()[0].clone(), + [ + WriteDescriptorSet::sampler(0, sampler.clone()), + WriteDescriptorSet::image_view_with_layout( + 1, + DescriptorImageViewInfo { + image_view: bloom_texture_view, + image_layout: ImageLayout::General, + }, + ), + WriteDescriptorSet::image_view_with_layout_array( + 2, + 0, + bloom_mip_chain_views.map(|image_view| DescriptorImageViewInfo { + image_view, + image_layout: ImageLayout::General, + }), + ), + ], + [], + ) + .unwrap(); + + (bloom_image_id, descriptor_set.into()) +} diff --git a/examples/bloom/scene.rs b/examples/bloom/scene.rs new file mode 100644 index 0000000000..014fee8fbf --- /dev/null +++ b/examples/bloom/scene.rs @@ -0,0 +1,259 @@ +use crate::RenderContext; +use std::{alloc::Layout, mem, slice, sync::Arc}; +use vulkano::{ + buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage}, + command_buffer::RenderPassBeginInfo, + format::Format, + image::{ + view::{ImageView, ImageViewCreateInfo}, + ImageAspects, ImageSubresourceRange, ImageUsage, + }, + memory::allocator::{AllocationCreateInfo, DeviceLayout, MemoryTypeFilter}, + pipeline::{ + graphics::{ + color_blend::{ColorBlendAttachmentState, ColorBlendState}, + input_assembly::InputAssemblyState, + multisample::MultisampleState, + rasterization::RasterizationState, + vertex_input::{Vertex, VertexDefinition}, + viewport::ViewportState, + GraphicsPipelineCreateInfo, + }, + DynamicState, GraphicsPipeline, PipelineShaderStageCreateInfo, + }, + render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, +}; +use vulkano_taskgraph::{ + command_buffer::RecordingCommandBuffer, resource::HostAccessType, Id, Task, TaskContext, + TaskResult, +}; + +pub struct SceneTask { + render_pass: Arc, + pipeline: Arc, + framebuffer: Arc, + vertex_buffer_id: Id, +} + +impl SceneTask { + pub fn new(rcx: &RenderContext) -> Self { + let render_pass = vulkano::single_pass_renderpass!( + rcx.device.clone(), + attachments: { + color: { + format: Format::R32_UINT, + samples: 1, + load_op: Clear, + store_op: Store, + }, + }, + pass: { + color: [color], + depth_stencil: {}, + }, + ) + .unwrap(); + + let pipeline = { + let vs = vs::load(rcx.device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + let fs = fs::load(rcx.device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + let vertex_input_state = MyVertex::per_vertex().definition(&vs).unwrap(); + let stages = [ + PipelineShaderStageCreateInfo::new(vs), + PipelineShaderStageCreateInfo::new(fs), + ]; + let subpass = Subpass::from(render_pass.clone(), 0).unwrap(); + + GraphicsPipeline::new( + rcx.device.clone(), + None, + GraphicsPipelineCreateInfo { + stages: stages.into_iter().collect(), + vertex_input_state: Some(vertex_input_state), + input_assembly_state: Some(InputAssemblyState::default()), + viewport_state: Some(ViewportState::default()), + rasterization_state: Some(RasterizationState::default()), + multisample_state: Some(MultisampleState::default()), + color_blend_state: Some(ColorBlendState::with_attachment_states( + subpass.num_color_attachments(), + ColorBlendAttachmentState::default(), + )), + dynamic_state: [DynamicState::Viewport].into_iter().collect(), + subpass: Some(subpass.into()), + ..GraphicsPipelineCreateInfo::layout(rcx.pipeline_layout.clone()) + }, + ) + .unwrap() + }; + + let framebuffer = window_size_dependent_setup(rcx, &render_pass); + + let vertices = [ + MyVertex { + position: [-0.5, 0.5], + }, + MyVertex { + position: [0.5, 0.5], + }, + MyVertex { + position: [0.0, -0.5], + }, + ]; + let vertex_buffer_id = rcx + .resources + .create_buffer( + BufferCreateInfo { + usage: BufferUsage::VERTEX_BUFFER, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + DeviceLayout::from_layout(Layout::for_value(&vertices)).unwrap(), + ) + .unwrap(); + + unsafe { + vulkano_taskgraph::execute( + &rcx.queue, + &rcx.resources, + rcx.flight_id, + |_cbf, tcx| { + tcx.write_buffer::<[MyVertex]>(vertex_buffer_id, ..)? + .copy_from_slice(&vertices); + + Ok(()) + }, + [(vertex_buffer_id, HostAccessType::Write)], + [], + [], + ) + } + .unwrap(); + + SceneTask { + render_pass, + pipeline, + framebuffer, + vertex_buffer_id, + } + } + + pub fn handle_resize(&mut self, rcx: &RenderContext) { + let framebuffer = window_size_dependent_setup(rcx, &self.render_pass); + + let flight = rcx.resources.flight(rcx.flight_id).unwrap(); + flight.destroy_object(mem::replace(&mut self.framebuffer, framebuffer)); + } + + pub fn cleanup(&mut self, rcx: &RenderContext) { + let flight = rcx.resources.flight(rcx.flight_id).unwrap(); + flight.destroy_object(self.framebuffer.clone()); + } +} + +impl Task for SceneTask { + type World = RenderContext; + + unsafe fn execute( + &self, + cbf: &mut RecordingCommandBuffer<'_>, + _tcx: &mut TaskContext<'_>, + rcx: &Self::World, + ) -> TaskResult { + cbf.as_raw().begin_render_pass( + &RenderPassBeginInfo { + clear_values: vec![Some([0u32; 4].into())], + ..RenderPassBeginInfo::framebuffer(self.framebuffer.clone()) + }, + &Default::default(), + )?; + cbf.set_viewport(0, slice::from_ref(&rcx.viewport))?; + cbf.bind_pipeline_graphics(&self.pipeline)?; + cbf.bind_vertex_buffers(0, &[self.vertex_buffer_id], &[0], &[], &[])?; + + unsafe { cbf.draw(3, 1, 0, 0) }?; + + cbf.as_raw().end_render_pass(&Default::default())?; + + Ok(()) + } +} + +#[derive(Clone, Copy, BufferContents, Vertex)] +#[repr(C)] +struct MyVertex { + #[format(R32G32_SFLOAT)] + position: [f32; 2], +} + +mod vs { + vulkano_shaders::shader! { + ty: "vertex", + src: r" + #version 450 + + layout(location = 0) in vec2 position; + + void main() { + gl_Position = vec4(position, 0.0, 1.0); + } + ", + } +} + +mod fs { + vulkano_shaders::shader! { + ty: "fragment", + src: r" + #version 450 + #include + + layout(location = 0) out uint f_color; + + void main() { + f_color = convertToSharedExponent(vec3(2.0, 0.0, 0.0)); + } + ", + include: ["."], + } +} + +fn window_size_dependent_setup( + rcx: &RenderContext, + render_pass: &Arc, +) -> Arc { + let image_state = rcx.resources.image(rcx.bloom_image_id).unwrap(); + let image = image_state.image(); + let view = ImageView::new( + image.clone(), + ImageViewCreateInfo { + format: Format::R32_UINT, + subresource_range: ImageSubresourceRange { + aspects: ImageAspects::COLOR, + mip_levels: 0..1, + array_layers: 0..1, + }, + usage: ImageUsage::COLOR_ATTACHMENT, + ..Default::default() + }, + ) + .unwrap(); + + Framebuffer::new( + render_pass.clone(), + FramebufferCreateInfo { + attachments: vec![view], + ..Default::default() + }, + ) + .unwrap() +} diff --git a/examples/bloom/shared_exponent.glsl b/examples/bloom/shared_exponent.glsl new file mode 100644 index 0000000000..b2085076a0 --- /dev/null +++ b/examples/bloom/shared_exponent.glsl @@ -0,0 +1,26 @@ +// https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_shared_exponent.txt +uint convertToSharedExponent(vec3 color) { + const int MAX = 65408; + const int BIAS = 15; + const int MANTISSA_BITS = 9; + const int MANTISSA_VALUES = 512; + + vec3 clamped_color = clamp(color, vec3(0.0), vec3(MAX)); + float max_clamped_component = max(clamped_color.r, max(clamped_color.g, clamped_color.b)); + int max_clamped_exponent = int((floatBitsToUint(max_clamped_component) >> 23) & 0xFF) - 127; + int shared_exponent = max(-BIAS - 1, max_clamped_exponent) + 1 + BIAS; + float divisor = exp2(float(shared_exponent - BIAS - MANTISSA_BITS)); + int max_shared_component = int(floor(max_clamped_component / divisor + 0.5)); + + if (max_shared_component == MANTISSA_VALUES) { + shared_exponent += 1; + divisor *= 2; + } + + vec3 shared_color = floor(clamped_color / divisor + 0.5); + + return (uint(shared_exponent) << 27) + | (uint(shared_color.b) << 18) + | (uint(shared_color.g) << 9) + | (uint(shared_color.r) << 0); +} diff --git a/examples/bloom/tonemap.glsl b/examples/bloom/tonemap.glsl new file mode 100644 index 0000000000..cc9bc0e05d --- /dev/null +++ b/examples/bloom/tonemap.glsl @@ -0,0 +1,40 @@ +#version 450 + +layout(location = 0) in vec2 v_tex_coords; + +layout(location = 0) out vec4 f_color; + +layout(set = 0, binding = 0) uniform sampler bloom_sampler; +layout(set = 0, binding = 1) uniform texture2D bloom_texture; + +layout(push_constant) uniform PushConstants { + float exposure; +}; + +const mat3 ACES_INPUT_MATRIX = { + { 0.59719, 0.07600, 0.02840 }, + { 0.35458, 0.90834, 0.13383 }, + { 0.04823, 0.01566, 0.83777 }, +}; + +const mat3 ACES_OUTPUT_MATRIX = { + { 1.60475, -0.10208, -0.00327 }, + { -0.53108, 1.10813, -0.07276 }, + { -0.07367, -0.00605, 1.07602 }, +}; + +vec3 rrtAndOdtFit(vec3 v) { + vec3 a = v * (v + 0.0245786) - 0.000090537; + vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081; + return a / b; +} + +void main() { + vec4 hdr_color = exposure * texture(sampler2D(bloom_texture, bloom_sampler), v_tex_coords); + + vec3 color = ACES_INPUT_MATRIX * hdr_color.rgb; + color = rrtAndOdtFit(color); + color = ACES_OUTPUT_MATRIX * color; + + f_color = vec4(color, 1.0); +} diff --git a/examples/bloom/tonemap.rs b/examples/bloom/tonemap.rs new file mode 100644 index 0000000000..3900f40253 --- /dev/null +++ b/examples/bloom/tonemap.rs @@ -0,0 +1,212 @@ +use crate::RenderContext; +use std::{mem, slice, sync::Arc}; +use vulkano::{ + command_buffer::RenderPassBeginInfo, + image::view::ImageView, + pipeline::{ + graphics::{ + color_blend::{ColorBlendAttachmentState, ColorBlendState}, + input_assembly::InputAssemblyState, + multisample::MultisampleState, + rasterization::RasterizationState, + vertex_input::VertexInputState, + viewport::ViewportState, + GraphicsPipelineCreateInfo, + }, + DynamicState, GraphicsPipeline, PipelineBindPoint, PipelineShaderStageCreateInfo, + }, + render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, + swapchain::Swapchain, +}; +use vulkano_taskgraph::{ + command_buffer::RecordingCommandBuffer, Id, Task, TaskContext, TaskResult, +}; + +const EXPOSURE: f32 = 1.0; + +pub struct TonemapTask { + render_pass: Arc, + pipeline: Arc, + framebuffers: Vec>, + swapchain_id: Id, +} + +impl TonemapTask { + pub fn new(rcx: &RenderContext, swapchain_id: Id) -> Self { + let render_pass = vulkano::single_pass_renderpass!( + rcx.device.clone(), + attachments: { + color: { + format: rcx.swapchain_format, + samples: 1, + load_op: DontCare, + store_op: Store, + }, + }, + pass: { + color: [color], + depth_stencil: {}, + }, + ) + .unwrap(); + + let pipeline = { + let vs = vs::load(rcx.device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + let fs = fs::load(rcx.device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + let stages = [ + PipelineShaderStageCreateInfo::new(vs), + PipelineShaderStageCreateInfo::new(fs), + ]; + let subpass = Subpass::from(render_pass.clone(), 0).unwrap(); + + GraphicsPipeline::new( + rcx.device.clone(), + None, + GraphicsPipelineCreateInfo { + stages: stages.into_iter().collect(), + vertex_input_state: Some(VertexInputState::default()), + input_assembly_state: Some(InputAssemblyState::default()), + viewport_state: Some(ViewportState::default()), + rasterization_state: Some(RasterizationState::default()), + multisample_state: Some(MultisampleState::default()), + color_blend_state: Some(ColorBlendState::with_attachment_states( + subpass.num_color_attachments(), + ColorBlendAttachmentState::default(), + )), + dynamic_state: [DynamicState::Viewport].into_iter().collect(), + subpass: Some(subpass.into()), + ..GraphicsPipelineCreateInfo::layout(rcx.pipeline_layout.clone()) + }, + ) + .unwrap() + }; + + let framebuffers = window_size_dependent_setup(rcx, &render_pass); + + TonemapTask { + render_pass, + pipeline, + framebuffers, + swapchain_id, + } + } + + pub fn handle_resize(&mut self, rcx: &RenderContext) { + let framebuffers = window_size_dependent_setup(rcx, &self.render_pass); + + let flight = rcx.resources.flight(rcx.flight_id).unwrap(); + flight.destroy_objects(mem::replace(&mut self.framebuffers, framebuffers)); + } + + pub fn cleanup(&mut self, rcx: &RenderContext) { + let flight = rcx.resources.flight(rcx.flight_id).unwrap(); + flight.destroy_objects(self.framebuffers.drain(..)); + } +} + +impl Task for TonemapTask { + type World = RenderContext; + + unsafe fn execute( + &self, + cbf: &mut RecordingCommandBuffer<'_>, + tcx: &mut TaskContext<'_>, + rcx: &Self::World, + ) -> TaskResult { + cbf.as_raw().bind_descriptor_sets( + PipelineBindPoint::Graphics, + &rcx.pipeline_layout, + 0, + slice::from_ref(&rcx.descriptor_set), + )?; + + let swapchain_state = tcx.swapchain(self.swapchain_id)?; + let image_index = swapchain_state.current_image_index().unwrap(); + + cbf.as_raw().begin_render_pass( + &RenderPassBeginInfo { + clear_values: vec![None], + ..RenderPassBeginInfo::framebuffer(self.framebuffers[image_index as usize].clone()) + }, + &Default::default(), + )?; + cbf.set_viewport(0, slice::from_ref(&rcx.viewport))?; + cbf.bind_pipeline_graphics(&self.pipeline)?; + cbf.push_constants( + &rcx.pipeline_layout, + 0, + &fs::PushConstants { exposure: EXPOSURE }, + )?; + + unsafe { cbf.draw(3, 1, 0, 0) }?; + + cbf.as_raw().end_render_pass(&Default::default())?; + + Ok(()) + } +} + +mod vs { + vulkano_shaders::shader! { + ty: "vertex", + src: r" + #version 450 + + const vec2[3] POSITIONS = { + vec2(-1.0, -1.0), + vec2(-1.0, 3.0), + vec2( 3.0, -1.0), + }; + + const vec2[3] TEX_COORDS = { + vec2(0.0, 0.0), + vec2(0.0, 2.0), + vec2(2.0, 0.0), + }; + + layout(location = 0) out vec2 v_tex_coords; + + void main() { + gl_Position = vec4(POSITIONS[gl_VertexIndex], 0.0, 1.0); + v_tex_coords = TEX_COORDS[gl_VertexIndex]; + } + ", + } +} + +mod fs { + vulkano_shaders::shader! { + ty: "fragment", + path: "tonemap.glsl", + include: ["."], + } +} + +fn window_size_dependent_setup( + rcx: &RenderContext, + render_pass: &Arc, +) -> Vec> { + let swapchain_state = rcx.resources.swapchain(rcx.swapchain_id).unwrap(); + let images = swapchain_state.images(); + + images + .iter() + .map(|image| { + let view = ImageView::new_default(image.clone()).unwrap(); + Framebuffer::new( + render_pass.clone(), + FramebufferCreateInfo { + attachments: vec![view], + ..Default::default() + }, + ) + .unwrap() + }) + .collect::>() +} diff --git a/examples/bloom/upsample.glsl b/examples/bloom/upsample.glsl new file mode 100644 index 0000000000..42b018ec18 --- /dev/null +++ b/examples/bloom/upsample.glsl @@ -0,0 +1,61 @@ +#version 450 +#include + +const uint MAX_BLOOM_MIP_LEVELS = 6; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0) uniform sampler bloom_sampler; +layout(set = 0, binding = 1) uniform texture2D bloom_texture; +layout(set = 0, binding = 2, r32ui) uniform uimage2D bloom_mip_chain[MAX_BLOOM_MIP_LEVELS]; + +layout(push_constant) uniform PushConstants { + uint dst_mip_level; + float intensity; +}; + +uint src_mip_level = dst_mip_level + 1; + +vec3 sample1(vec2 uv) { + return textureLod(sampler2D(bloom_texture, bloom_sampler), uv, src_mip_level).rgb; +} + +// 9-tap tent filter. +vec3 upsampleTent9(vec2 uv, vec2 src_texel_size) { + vec3 color; + color = sample1(uv + vec2(-1.0, -1.0) * src_texel_size) * 1.0; + color += sample1(uv + vec2( 0.0, -1.0) * src_texel_size) * 2.0; + color += sample1(uv + vec2( 1.0, -1.0) * src_texel_size) * 1.0; + color += sample1(uv + vec2(-1.0, 0.0) * src_texel_size) * 2.0; + color += sample1(uv + vec2( 0.0, 0.0) * src_texel_size) * 4.0; + color += sample1(uv + vec2( 1.0, 0.0) * src_texel_size) * 2.0; + color += sample1(uv + vec2(-1.0, 1.0) * src_texel_size) * 1.0; + color += sample1(uv + vec2( 0.0, 1.0) * src_texel_size) * 2.0; + color += sample1(uv + vec2( 1.0, 1.0) * src_texel_size) * 1.0; + + return color * (1.0 / 16.0); +} + +void blend(vec2 uv, ivec2 dst_coord, vec3 color) { + color += textureLod(sampler2D(bloom_texture, bloom_sampler), uv, dst_mip_level).rgb; + uint packed = convertToSharedExponent(color); + imageStore(bloom_mip_chain[dst_mip_level], dst_coord, uvec4(packed, 0, 0, 0)); +} + +void main() { + ivec2 dst_coord = ivec2(gl_GlobalInvocationID.xy); + ivec2 dst_size = imageSize(bloom_mip_chain[dst_mip_level]); + + if (dst_coord.x > dst_size.x || dst_coord.y > dst_size.y) { + return; + } + + ivec2 src_size = textureSize(sampler2D(bloom_texture, bloom_sampler), int(src_mip_level)); + vec2 src_texel_size = 1.0 / vec2(src_size); + vec2 uv = (vec2(dst_coord) + 0.5) / vec2(dst_size); + vec3 color = upsampleTent9(uv, src_texel_size); + + color *= intensity; + + blend(uv, dst_coord, color); +} diff --git a/vulkano-taskgraph/src/command_buffer/commands/bind_push.rs b/vulkano-taskgraph/src/command_buffer/commands/bind_push.rs new file mode 100644 index 0000000000..a15a0a997f --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/commands/bind_push.rs @@ -0,0 +1,278 @@ +use crate::{ + command_buffer::{RecordingCommandBuffer, Result}, + Id, +}; +use ash::vk; +use smallvec::SmallVec; +use std::{ffi::c_void, mem, ptr, sync::Arc}; +use vulkano::{ + self, + buffer::{Buffer, BufferContents, IndexType}, + device::DeviceOwned, + pipeline::{ComputePipeline, GraphicsPipeline, PipelineLayout}, + DeviceSize, Version, VulkanObject, +}; + +/// # Commands to bind or push state for pipeline execution commands +/// +/// These commands require a queue with a pipeline type that uses the given state. +impl RecordingCommandBuffer<'_> { + /// Binds an index buffer for future indexed draw calls. + pub unsafe fn bind_index_buffer( + &mut self, + buffer: Id, + offset: DeviceSize, + size: DeviceSize, + index_type: IndexType, + ) -> Result<&mut Self> { + Ok(unsafe { self.bind_index_buffer_unchecked(buffer, offset, size, index_type) }) + } + + pub unsafe fn bind_index_buffer_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + size: DeviceSize, + index_type: IndexType, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + + let fns = self.device().fns(); + + if self.device().enabled_extensions().khr_maintenance5 { + unsafe { + (fns.khr_maintenance5.cmd_bind_index_buffer2_khr)( + self.handle(), + buffer.handle(), + offset, + size, + index_type.into(), + ) + }; + } else { + unsafe { + (fns.v1_0.cmd_bind_index_buffer)( + self.handle(), + buffer.handle(), + offset, + index_type.into(), + ) + }; + } + + self + } + + /// Binds a compute pipeline for future dispatch calls. + pub unsafe fn bind_pipeline_compute( + &mut self, + pipeline: &Arc, + ) -> Result<&mut Self> { + Ok(unsafe { self.bind_pipeline_compute_unchecked(pipeline) }) + } + + pub unsafe fn bind_pipeline_compute_unchecked( + &mut self, + pipeline: &Arc, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_bind_pipeline)( + self.handle(), + vk::PipelineBindPoint::COMPUTE, + pipeline.handle(), + ) + }; + + self.death_row.push(pipeline.clone()); + + self + } + + /// Binds a graphics pipeline for future draw calls. + pub unsafe fn bind_pipeline_graphics( + &mut self, + pipeline: &Arc, + ) -> Result<&mut Self> { + Ok(unsafe { self.bind_pipeline_graphics_unchecked(pipeline) }) + } + + pub unsafe fn bind_pipeline_graphics_unchecked( + &mut self, + pipeline: &Arc, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_bind_pipeline)( + self.handle(), + vk::PipelineBindPoint::GRAPHICS, + pipeline.handle(), + ) + }; + + self.death_row.push(pipeline.clone()); + + self + } + + /// Binds vertex buffers for future draw calls. + pub unsafe fn bind_vertex_buffers( + &mut self, + first_binding: u32, + buffers: &[Id], + offsets: &[DeviceSize], + sizes: &[DeviceSize], + strides: &[DeviceSize], + ) -> Result<&mut Self> { + Ok(unsafe { + self.bind_vertex_buffers_unchecked(first_binding, buffers, offsets, sizes, strides) + }) + } + + pub unsafe fn bind_vertex_buffers_unchecked( + &mut self, + first_binding: u32, + buffers: &[Id], + offsets: &[DeviceSize], + sizes: &[DeviceSize], + strides: &[DeviceSize], + ) -> &mut Self { + if buffers.is_empty() { + return self; + } + + let buffers_vk = buffers + .iter() + .map(|&buffer| unsafe { self.accesses.buffer_unchecked(buffer) }.handle()) + .collect::>(); + + let device = self.device(); + let fns = self.device().fns(); + + if device.api_version() >= Version::V1_3 + || device.enabled_extensions().ext_extended_dynamic_state + || device.enabled_extensions().ext_shader_object + { + let cmd_bind_vertex_buffers2 = if device.api_version() >= Version::V1_3 { + fns.v1_3.cmd_bind_vertex_buffers2 + } else if device.enabled_extensions().ext_extended_dynamic_state { + fns.ext_extended_dynamic_state.cmd_bind_vertex_buffers2_ext + } else { + fns.ext_shader_object.cmd_bind_vertex_buffers2_ext + }; + + unsafe { + cmd_bind_vertex_buffers2( + self.handle(), + first_binding, + buffers_vk.len() as u32, + buffers_vk.as_ptr(), + offsets.as_ptr(), + if sizes.is_empty() { + ptr::null() + } else { + sizes.as_ptr() + }, + if strides.is_empty() { + ptr::null() + } else { + strides.as_ptr() + }, + ) + }; + } else { + unsafe { + (fns.v1_0.cmd_bind_vertex_buffers)( + self.handle(), + first_binding, + buffers_vk.len() as u32, + buffers_vk.as_ptr(), + offsets.as_ptr(), + ) + }; + } + + self + } + + /// Sets push constants for future dispatch or draw calls. + pub unsafe fn push_constants( + &mut self, + layout: &Arc, + offset: u32, + values: &(impl BufferContents + ?Sized), + ) -> Result<&mut Self> { + Ok(unsafe { self.push_constants_unchecked(layout, offset, values) }) + } + + pub unsafe fn push_constants_unchecked( + &mut self, + layout: &Arc, + offset: u32, + values: &(impl BufferContents + ?Sized), + ) -> &mut Self { + unsafe { + self.push_constants_unchecked_inner( + layout, + offset, + <*const _>::cast(values), + mem::size_of_val(values) as u32, + ) + } + } + + unsafe fn push_constants_unchecked_inner( + &mut self, + layout: &Arc, + offset: u32, + values: *const c_void, + size: u32, + ) -> &mut Self { + if size == 0 { + return self; + } + + let fns = self.device().fns(); + let mut current_offset = offset; + let mut remaining_size = size; + + for range in layout + .push_constant_ranges_disjoint() + .iter() + .skip_while(|range| range.offset + range.size <= offset) + { + // There is a gap between ranges, but the passed `values` contain some bytes in this + // gap. + if range.offset > current_offset { + std::process::abort(); + } + + // Push the minimum of the whole remaining data and the part until the end of this + // range. + let push_size = remaining_size.min(range.offset + range.size - current_offset); + let push_offset = (current_offset - offset) as usize; + debug_assert!(push_offset < size as usize); + let push_values = unsafe { values.add(push_offset) }; + + unsafe { + (fns.v1_0.cmd_push_constants)( + self.handle(), + layout.handle(), + range.stages.into(), + current_offset, + push_size, + push_values, + ) + }; + + current_offset += push_size; + remaining_size -= push_size; + + if remaining_size == 0 { + break; + } + } + + self + } +} diff --git a/vulkano-taskgraph/src/command_buffer/commands/clear.rs b/vulkano-taskgraph/src/command_buffer/commands/clear.rs new file mode 100644 index 0000000000..f5170be8dd --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/commands/clear.rs @@ -0,0 +1,345 @@ +use crate::{ + command_buffer::{RecordingCommandBuffer, Result}, + resource::{AccessType, ImageLayoutType}, + Id, +}; +use ash::vk; +use smallvec::SmallVec; +use std::{ffi::c_void, mem}; +use vulkano::{ + buffer::{Buffer, BufferContents}, + device::DeviceOwned, + format::{ClearColorValue, ClearDepthStencilValue}, + image::{Image, ImageSubresourceRange}, + DeviceSize, VulkanObject, +}; + +/// # Commands to fill resources with new data +impl RecordingCommandBuffer<'_> { + /// Clears a color image with a specific value. + pub unsafe fn clear_color_image( + &mut self, + clear_info: &ClearColorImageInfo<'_>, + ) -> Result<&mut Self> { + Ok(unsafe { self.clear_color_image_unchecked(clear_info) }) + } + + pub unsafe fn clear_color_image_unchecked( + &mut self, + clear_info: &ClearColorImageInfo<'_>, + ) -> &mut Self { + let &ClearColorImageInfo { + image, + image_layout, + clear_value, + regions, + _ne: _, + } = clear_info; + + let image = unsafe { self.accesses.image_unchecked(image) }; + let image_layout = AccessType::ClearTransferWrite.image_layout(image_layout); + + let fns = self.device().fns(); + let cmd_clear_color_image = fns.v1_0.cmd_clear_color_image; + + if regions.is_empty() { + let region_vk = image.subresource_range().into(); + + unsafe { + cmd_clear_color_image( + self.handle(), + image.handle(), + image_layout.into(), + &clear_value.into(), + 1, + ®ion_vk, + ) + }; + } else { + let regions_vk = regions + .iter() + .cloned() + .map(vk::ImageSubresourceRange::from) + .collect::>(); + + unsafe { + cmd_clear_color_image( + self.handle(), + image.handle(), + image_layout.into(), + &clear_value.into(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } + + self + } + + /// Clears a depth/stencil image with a specific value. + pub unsafe fn clear_depth_stencil_image( + &mut self, + clear_info: &ClearDepthStencilImageInfo<'_>, + ) -> Result<&mut Self> { + Ok(unsafe { self.clear_depth_stencil_image_unchecked(clear_info) }) + } + + pub unsafe fn clear_depth_stencil_image_unchecked( + &mut self, + clear_info: &ClearDepthStencilImageInfo<'_>, + ) -> &mut Self { + let &ClearDepthStencilImageInfo { + image, + image_layout, + clear_value, + regions, + _ne: _, + } = clear_info; + + let image = unsafe { self.accesses.image_unchecked(image) }; + let image_layout = AccessType::ClearTransferWrite.image_layout(image_layout); + + let fns = self.device().fns(); + let cmd_clear_depth_stencil_image = fns.v1_0.cmd_clear_depth_stencil_image; + + if regions.is_empty() { + let region_vk = image.subresource_range().into(); + + unsafe { + cmd_clear_depth_stencil_image( + self.handle(), + image.handle(), + image_layout.into(), + &clear_value.into(), + 1, + ®ion_vk, + ) + }; + } else { + let regions_vk = regions + .iter() + .cloned() + .map(vk::ImageSubresourceRange::from) + .collect::>(); + + unsafe { + cmd_clear_depth_stencil_image( + self.handle(), + image.handle(), + image_layout.into(), + &clear_value.into(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } + + self + } + + /// Fills a region of a buffer with repeated copies of a value. + /// + /// This function is similar to the `memset` function in C. The `data` parameter is a number + /// that will be repeatedly written through the entire buffer. + pub unsafe fn fill_buffer(&mut self, fill_info: &FillBufferInfo<'_>) -> Result<&mut Self> { + Ok(unsafe { self.fill_buffer_unchecked(fill_info) }) + } + + pub unsafe fn fill_buffer_unchecked(&mut self, fill_info: &FillBufferInfo<'_>) -> &mut Self { + let &FillBufferInfo { + dst_buffer, + dst_offset, + mut size, + data, + _ne: _, + } = fill_info; + + let dst_buffer = unsafe { self.accesses.buffer_unchecked(dst_buffer) }; + + if size == 0 { + size = dst_buffer.size() & !3; + } + + let fns = self.device().fns(); + let cmd_fill_buffer = fns.v1_0.cmd_fill_buffer; + unsafe { cmd_fill_buffer(self.handle(), dst_buffer.handle(), dst_offset, size, data) }; + + self + } + + /// Writes data to a region of a buffer. + pub unsafe fn update_buffer( + &mut self, + dst_buffer: Id, + dst_offset: DeviceSize, + data: &(impl BufferContents + ?Sized), + ) -> Result<&mut Self> { + Ok(unsafe { self.update_buffer_unchecked(dst_buffer, dst_offset, data) }) + } + + pub unsafe fn update_buffer_unchecked( + &mut self, + dst_buffer: Id, + dst_offset: DeviceSize, + data: &(impl BufferContents + ?Sized), + ) -> &mut Self { + unsafe { + self.update_buffer_inner( + dst_buffer, + dst_offset, + <*const _>::cast(data), + mem::size_of_val(data) as DeviceSize, + ) + } + } + + unsafe fn update_buffer_inner( + &mut self, + dst_buffer: Id, + dst_offset: DeviceSize, + data: *const c_void, + data_size: DeviceSize, + ) -> &mut Self { + if data_size == 0 { + return self; + } + + let dst_buffer = unsafe { self.accesses.buffer_unchecked(dst_buffer) }; + + let fns = self.device().fns(); + let cmd_update_buffer = fns.v1_0.cmd_update_buffer; + unsafe { + cmd_update_buffer( + self.handle(), + dst_buffer.handle(), + dst_offset, + data_size, + data, + ) + }; + + self + } +} + +/// Parameters to clear a color image. +#[derive(Clone, Debug)] +pub struct ClearColorImageInfo<'a> { + /// The image to clear. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub image: Id, + + /// The layout used for `image` during the clear operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub image_layout: ImageLayoutType, + + /// The color value to clear the image to. + /// + /// The default value is `ClearColorValue::Float([0.0; 4])`. + pub clear_value: ClearColorValue, + + /// The subresource ranges of `image` to clear. + /// + /// The default value is a single region, covering the whole image. + pub regions: &'a [ImageSubresourceRange], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for ClearColorImageInfo<'_> { + #[inline] + fn default() -> Self { + ClearColorImageInfo { + image: Id::INVALID, + image_layout: ImageLayoutType::Optimal, + clear_value: ClearColorValue::Float([0.0; 4]), + regions: &[], + _ne: crate::NE, + } + } +} + +/// Parameters to clear a depth/stencil image. +#[derive(Clone, Debug)] +pub struct ClearDepthStencilImageInfo<'a> { + /// The image to clear. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub image: Id, + + /// The layout used for `image` during the clear operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub image_layout: ImageLayoutType, + + /// The depth/stencil values to clear the image to. + /// + /// The default value is zero for both. + pub clear_value: ClearDepthStencilValue, + + /// The subresource ranges of `image` to clear. + /// + /// The default value is a single region, covering the whole image. + pub regions: &'a [ImageSubresourceRange], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for ClearDepthStencilImageInfo<'_> { + #[inline] + fn default() -> Self { + ClearDepthStencilImageInfo { + image: Id::INVALID, + image_layout: ImageLayoutType::Optimal, + clear_value: ClearDepthStencilValue::default(), + regions: &[], + _ne: crate::NE, + } + } +} + +/// Parameters to fill a region of a buffer with repeated copies of a value. +#[derive(Clone, Debug)] +pub struct FillBufferInfo<'a> { + /// The buffer to fill. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub dst_buffer: Id, + + /// The offset in bytes from the start of `dst_buffer` that filling will start from. + /// + /// This must be a multiple of 4. + /// + /// The default value is `0`. + pub dst_offset: DeviceSize, + + /// The number of bytes to fill. + /// + /// This must be a multiple of 4. + /// + /// The default value is the size of `dst_buffer`, rounded down to the nearest multiple of 4. + pub size: DeviceSize, + + /// The data to fill with. + /// + /// The default value is `0`. + pub data: u32, + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for FillBufferInfo<'_> { + #[inline] + fn default() -> Self { + FillBufferInfo { + dst_buffer: Id::INVALID, + dst_offset: 0, + size: 0, + data: 0, + _ne: crate::NE, + } + } +} diff --git a/vulkano-taskgraph/src/command_buffer/commands/copy.rs b/vulkano-taskgraph/src/command_buffer/commands/copy.rs new file mode 100644 index 0000000000..624475b1c9 --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/commands/copy.rs @@ -0,0 +1,1531 @@ +use crate::{ + command_buffer::{RecordingCommandBuffer, Result}, + resource::{AccessType, ImageLayoutType}, + Id, +}; +use ash::vk; +use smallvec::SmallVec; +use std::cmp; +use vulkano::{ + buffer::Buffer, + device::DeviceOwned, + image::{sampler::Filter, Image, ImageAspects, ImageSubresourceLayers}, + DeviceSize, Version, VulkanObject, +}; + +/// # Commands to transfer data between resources +impl RecordingCommandBuffer<'_> { + /// Copies data from a buffer to another buffer. + pub unsafe fn copy_buffer( + &mut self, + copy_buffer_info: &CopyBufferInfo<'_>, + ) -> Result<&mut Self> { + Ok(unsafe { self.copy_buffer_unchecked(copy_buffer_info) }) + } + + pub unsafe fn copy_buffer_unchecked( + &mut self, + copy_buffer_info: &CopyBufferInfo<'_>, + ) -> &mut Self { + let &CopyBufferInfo { + src_buffer, + dst_buffer, + regions, + _ne: _, + } = copy_buffer_info; + + let src_buffer = unsafe { self.accesses.buffer_unchecked(src_buffer) }; + let dst_buffer = unsafe { self.accesses.buffer_unchecked(dst_buffer) }; + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let cmd_copy_buffer2 = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_copy_buffer2 + } else { + fns.khr_copy_commands2.cmd_copy_buffer2_khr + }; + + if regions.is_empty() { + let regions_vk = [vk::BufferCopy2::default() + .src_offset(0) + .dst_offset(0) + .size(cmp::min(src_buffer.size(), dst_buffer.size()))]; + + let copy_buffer_info_vk = vk::CopyBufferInfo2::default() + .src_buffer(src_buffer.handle()) + .dst_buffer(dst_buffer.handle()) + .regions(®ions_vk); + + unsafe { cmd_copy_buffer2(self.handle(), ©_buffer_info_vk) }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &BufferCopy { + src_offset, + dst_offset, + size, + _ne, + } = region; + + vk::BufferCopy2::default() + .src_offset(src_offset) + .dst_offset(dst_offset) + .size(size) + }) + .collect::>(); + + let copy_buffer_info_vk = vk::CopyBufferInfo2::default() + .src_buffer(src_buffer.handle()) + .dst_buffer(dst_buffer.handle()) + .regions(®ions_vk); + + unsafe { cmd_copy_buffer2(self.handle(), ©_buffer_info_vk) }; + } + } else { + let cmd_copy_buffer = fns.v1_0.cmd_copy_buffer; + + if regions.is_empty() { + let region_vk = vk::BufferCopy { + src_offset: 0, + dst_offset: 0, + size: cmp::min(src_buffer.size(), dst_buffer.size()), + }; + + unsafe { + cmd_copy_buffer( + self.handle(), + src_buffer.handle(), + dst_buffer.handle(), + 1, + ®ion_vk, + ) + }; + } else { + let regions_vk = regions + .iter() + .map(|copy| { + let &BufferCopy { + src_offset, + dst_offset, + size, + _ne: _, + } = copy; + + vk::BufferCopy { + src_offset, + dst_offset, + size, + } + }) + .collect::>(); + + unsafe { + cmd_copy_buffer( + self.handle(), + src_buffer.handle(), + dst_buffer.handle(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } + } + + self + } + + /// Copies data from an image to another image. + /// + /// There are several restrictions: + /// + /// - The number of samples in the source and destination images must be equal. + /// - The size of the uncompressed element format of the source image must be equal to the + /// compressed element format of the destination. + /// - If you copy between depth, stencil or depth-stencil images, the format of both images + /// must match exactly. + /// - For two-dimensional images, the Z coordinate must be 0 for the image offsets and 1 for + /// the extent. Same for the Y coordinate for one-dimensional images. + /// - For non-array images, the base array layer must be 0 and the number of layers must be 1. + /// + /// If `layer_count` is greater than 1, the copy will happen between each individual layer as + /// if they were separate images. + pub unsafe fn copy_image(&mut self, copy_image_info: &CopyImageInfo<'_>) -> Result<&mut Self> { + Ok(unsafe { self.copy_image_unchecked(copy_image_info) }) + } + + pub unsafe fn copy_image_unchecked( + &mut self, + copy_image_info: &CopyImageInfo<'_>, + ) -> &mut Self { + let &CopyImageInfo { + src_image, + src_image_layout, + dst_image, + dst_image_layout, + regions, + _ne: _, + } = copy_image_info; + + let src_image = unsafe { self.accesses.image_unchecked(src_image) }; + let src_image_layout = AccessType::CopyTransferRead.image_layout(src_image_layout); + let dst_image = unsafe { self.accesses.image_unchecked(dst_image) }; + let dst_image_layout = AccessType::CopyTransferWrite.image_layout(dst_image_layout); + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let cmd_copy_image2 = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_copy_image2 + } else { + fns.khr_copy_commands2.cmd_copy_image2_khr + }; + + if regions.is_empty() { + let min_array_layers = cmp::min(src_image.array_layers(), dst_image.array_layers()); + let src_extent = src_image.extent(); + let dst_extent = dst_image.extent(); + let regions_vk = [vk::ImageCopy2::default() + .src_subresource( + ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + ) + .src_offset(convert_offset([0; 3])) + .dst_subresource( + ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..dst_image.subresource_layers() + } + .into(), + ) + .dst_offset(convert_offset([0; 3])) + .extent(convert_extent([ + cmp::min(src_extent[0], dst_extent[0]), + cmp::min(src_extent[1], dst_extent[1]), + cmp::min(src_extent[2], dst_extent[2]), + ]))]; + + let copy_image_info_vk = vk::CopyImageInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk); + + unsafe { cmd_copy_image2(self.handle(), ©_image_info_vk) }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &ImageCopy { + ref src_subresource, + src_offset, + ref dst_subresource, + dst_offset, + extent, + _ne: _, + } = region; + + vk::ImageCopy2::default() + .src_subresource(src_subresource.into()) + .src_offset(convert_offset(src_offset)) + .dst_subresource(dst_subresource.into()) + .dst_offset(convert_offset(dst_offset)) + .extent(convert_extent(extent)) + }) + .collect::>(); + + let copy_image_info_vk = vk::CopyImageInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk); + + unsafe { cmd_copy_image2(self.handle(), ©_image_info_vk) }; + } + } else { + let cmd_copy_image = fns.v1_0.cmd_copy_image; + + if regions.is_empty() { + let min_array_layers = cmp::min(src_image.array_layers(), dst_image.array_layers()); + let src_extent = src_image.extent(); + let dst_extent = dst_image.extent(); + let region_vk = vk::ImageCopy { + src_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + src_offset: convert_offset([0; 3]), + dst_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..dst_image.subresource_layers() + } + .into(), + dst_offset: convert_offset([0; 3]), + extent: convert_extent([ + cmp::min(src_extent[0], dst_extent[0]), + cmp::min(src_extent[1], dst_extent[1]), + cmp::min(src_extent[2], dst_extent[2]), + ]), + }; + + unsafe { + cmd_copy_image( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_image.handle(), + dst_image_layout.into(), + 1, + ®ion_vk, + ) + }; + } else { + let regions_vk: SmallVec<[_; 8]> = regions + .iter() + .map(|region| { + let &ImageCopy { + ref src_subresource, + src_offset, + ref dst_subresource, + dst_offset, + extent, + _ne: _, + } = region; + + vk::ImageCopy { + src_subresource: src_subresource.into(), + src_offset: convert_offset(src_offset), + dst_subresource: dst_subresource.into(), + dst_offset: convert_offset(dst_offset), + extent: convert_extent(extent), + } + }) + .collect(); + + unsafe { + cmd_copy_image( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_image.handle(), + dst_image_layout.into(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } + } + + self + } + + /// Copies from a buffer to an image. + pub unsafe fn copy_buffer_to_image( + &mut self, + copy_buffer_to_image_info: &CopyBufferToImageInfo<'_>, + ) -> Result<&mut Self> { + Ok(unsafe { self.copy_buffer_to_image_unchecked(copy_buffer_to_image_info) }) + } + + pub unsafe fn copy_buffer_to_image_unchecked( + &mut self, + copy_buffer_to_image_info: &CopyBufferToImageInfo<'_>, + ) -> &mut Self { + let &CopyBufferToImageInfo { + src_buffer, + dst_image, + dst_image_layout, + regions, + _ne: _, + } = copy_buffer_to_image_info; + + let src_buffer = unsafe { self.accesses.buffer_unchecked(src_buffer) }; + let dst_image = unsafe { self.accesses.image_unchecked(dst_image) }; + let dst_image_layout = AccessType::CopyTransferWrite.image_layout(dst_image_layout); + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let cmd_copy_buffer_to_image2 = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_copy_buffer_to_image2 + } else { + fns.khr_copy_commands2.cmd_copy_buffer_to_image2_khr + }; + + if regions.is_empty() { + let regions_vk = [vk::BufferImageCopy2::default() + .buffer_offset(0) + .buffer_row_length(0) + .buffer_image_height(0) + .image_subresource(dst_image.subresource_layers().into()) + .image_offset(convert_offset([0; 3])) + .image_extent(convert_extent(dst_image.extent()))]; + + let copy_buffer_to_image_info_vk = vk::CopyBufferToImageInfo2::default() + .src_buffer(src_buffer.handle()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk); + + unsafe { cmd_copy_buffer_to_image2(self.handle(), ©_buffer_to_image_info_vk) }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + ref image_subresource, + image_offset, + image_extent, + _ne: _, + } = region; + + vk::BufferImageCopy2::default() + .buffer_offset(buffer_offset) + .buffer_row_length(buffer_row_length) + .buffer_image_height(buffer_image_height) + .image_subresource(image_subresource.into()) + .image_offset(convert_offset(image_offset)) + .image_extent(convert_extent(image_extent)) + }) + .collect::>(); + + let copy_buffer_to_image_info_vk = vk::CopyBufferToImageInfo2::default() + .src_buffer(src_buffer.handle()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk); + + unsafe { cmd_copy_buffer_to_image2(self.handle(), ©_buffer_to_image_info_vk) }; + } + } else { + let cmd_copy_buffer_to_image = fns.v1_0.cmd_copy_buffer_to_image; + + if regions.is_empty() { + let region_vk = vk::BufferImageCopy { + buffer_offset: 0, + buffer_row_length: 0, + buffer_image_height: 0, + image_subresource: dst_image.subresource_layers().into(), + image_offset: convert_offset([0; 3]), + image_extent: convert_extent(dst_image.extent()), + }; + + unsafe { + cmd_copy_buffer_to_image( + self.handle(), + src_buffer.handle(), + dst_image.handle(), + dst_image_layout.into(), + 1, + ®ion_vk, + ) + }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + ref image_subresource, + image_offset, + image_extent, + _ne: _, + } = region; + + vk::BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + image_subresource: image_subresource.into(), + image_offset: convert_offset(image_offset), + image_extent: convert_extent(image_extent), + } + }) + .collect::>(); + + unsafe { + cmd_copy_buffer_to_image( + self.handle(), + src_buffer.handle(), + dst_image.handle(), + dst_image_layout.into(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } + } + + self + } + + /// Copies from an image to a buffer. + pub unsafe fn copy_image_to_buffer( + &mut self, + copy_image_to_buffer_info: &CopyImageToBufferInfo<'_>, + ) -> Result<&mut Self> { + Ok(unsafe { self.copy_image_to_buffer_unchecked(copy_image_to_buffer_info) }) + } + + pub unsafe fn copy_image_to_buffer_unchecked( + &mut self, + copy_image_to_buffer_info: &CopyImageToBufferInfo<'_>, + ) -> &mut Self { + let &CopyImageToBufferInfo { + src_image, + src_image_layout, + dst_buffer, + regions, + _ne: _, + } = copy_image_to_buffer_info; + + let src_image = unsafe { self.accesses.image_unchecked(src_image) }; + let src_image_layout = AccessType::CopyTransferRead.image_layout(src_image_layout); + let dst_buffer = unsafe { self.accesses.buffer_unchecked(dst_buffer) }; + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let cmd_copy_image_to_buffer2 = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_copy_image_to_buffer2 + } else { + fns.khr_copy_commands2.cmd_copy_image_to_buffer2_khr + }; + + if regions.is_empty() { + let regions_vk = [vk::BufferImageCopy2::default() + .buffer_offset(0) + .buffer_row_length(0) + .buffer_image_height(0) + .image_subresource(src_image.subresource_layers().into()) + .image_offset(convert_offset([0; 3])) + .image_extent(convert_extent(src_image.extent()))]; + + let copy_image_to_buffer_info_vk = vk::CopyImageToBufferInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_buffer(dst_buffer.handle()) + .regions(®ions_vk); + + unsafe { cmd_copy_image_to_buffer2(self.handle(), ©_image_to_buffer_info_vk) }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + ref image_subresource, + image_offset, + image_extent, + _ne: _, + } = region; + + vk::BufferImageCopy2::default() + .buffer_offset(buffer_offset) + .buffer_row_length(buffer_row_length) + .buffer_image_height(buffer_image_height) + .image_subresource(image_subresource.into()) + .image_offset(convert_offset(image_offset)) + .image_extent(convert_extent(image_extent)) + }) + .collect::>(); + + let copy_image_to_buffer_info_vk = vk::CopyImageToBufferInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_buffer(dst_buffer.handle()) + .regions(®ions_vk); + + unsafe { cmd_copy_image_to_buffer2(self.handle(), ©_image_to_buffer_info_vk) }; + } + } else { + let cmd_copy_image_to_buffer = fns.v1_0.cmd_copy_image_to_buffer; + + if regions.is_empty() { + let region_vk = vk::BufferImageCopy { + buffer_offset: 0, + buffer_row_length: 0, + buffer_image_height: 0, + image_subresource: src_image.subresource_layers().into(), + image_offset: convert_offset([0; 3]), + image_extent: convert_extent(src_image.extent()), + }; + + unsafe { + cmd_copy_image_to_buffer( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_buffer.handle(), + 1, + ®ion_vk, + ) + }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + ref image_subresource, + image_offset, + image_extent, + _ne: _, + } = region; + + vk::BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + image_subresource: image_subresource.into(), + image_offset: convert_offset(image_offset), + image_extent: convert_extent(image_extent), + } + }) + .collect::>(); + + unsafe { + cmd_copy_image_to_buffer( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_buffer.handle(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } + } + + self + } + + /// Blits an image to another. + /// + /// A *blit* is similar to an image copy operation, except that the portion of the image that + /// is transferred can be resized. You choose an area of the source and an area of the + /// destination, and the implementation will resize the area of the source so that it matches + /// the size of the area of the destination before writing it. + /// + /// Blit operations have several restrictions: + /// + /// - Blit operations are only allowed on queue families that support graphics operations. + /// - The format of the source and destination images must support blit operations, which + /// depends on the Vulkan implementation. Vulkan guarantees that some specific formats must + /// always be supported. See tables 52 to 61 of the specifications. + /// - Only single-sampled images are allowed. + /// - You can only blit between two images whose formats belong to the same type. The types + /// are: floating-point, signed integers, unsigned integers, depth-stencil. + /// - If you blit between depth, stencil or depth-stencil images, the format of both images + /// must match exactly. + /// - If you blit between depth, stencil or depth-stencil images, only the `Nearest` filter is + /// allowed. + /// - For two-dimensional images, the Z coordinate must be 0 for the top-left offset and 1 for + /// the bottom-right offset. Same for the Y coordinate for one-dimensional images. + /// - For non-array images, the base array layer must be 0 and the number of layers must be 1. + /// + /// If `layer_count` is greater than 1, the blit will happen between each individual layer as + /// if they were separate images. + pub unsafe fn blit_image(&mut self, blit_image_info: &BlitImageInfo<'_>) -> Result<&mut Self> { + Ok(unsafe { self.blit_image_unchecked(blit_image_info) }) + } + + pub unsafe fn blit_image_unchecked( + &mut self, + blit_image_info: &BlitImageInfo<'_>, + ) -> &mut Self { + let &BlitImageInfo { + src_image, + src_image_layout, + dst_image, + dst_image_layout, + regions, + filter, + _ne: _, + } = blit_image_info; + + let src_image = unsafe { self.accesses.image_unchecked(src_image) }; + let src_image_layout = AccessType::BlitTransferRead.image_layout(src_image_layout); + let dst_image = unsafe { self.accesses.image_unchecked(dst_image) }; + let dst_image_layout = AccessType::BlitTransferWrite.image_layout(dst_image_layout); + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let cmd_blit_image2 = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_blit_image2 + } else { + fns.khr_copy_commands2.cmd_blit_image2_khr + }; + + if regions.is_empty() { + let min_array_layers = cmp::min(src_image.array_layers(), dst_image.array_layers()); + let regions_vk = [vk::ImageBlit2::default() + .src_subresource( + ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + ) + .src_offsets([[0; 3], src_image.extent()].map(convert_offset)) + .dst_subresource( + ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + ) + .dst_offsets([[0; 3], dst_image.extent()].map(convert_offset))]; + + let blit_image_info_vk = vk::BlitImageInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk) + .filter(filter.into()); + + unsafe { cmd_blit_image2(self.handle(), &blit_image_info_vk) }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &ImageBlit { + ref src_subresource, + src_offsets, + ref dst_subresource, + dst_offsets, + _ne: _, + } = region; + + vk::ImageBlit2::default() + .src_subresource(src_subresource.into()) + .src_offsets(src_offsets.map(convert_offset)) + .dst_subresource(dst_subresource.into()) + .dst_offsets(dst_offsets.map(convert_offset)) + }) + .collect::>(); + + let blit_image_info_vk = vk::BlitImageInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk) + .filter(filter.into()); + + unsafe { cmd_blit_image2(self.handle(), &blit_image_info_vk) }; + } + } else { + let cmd_blit_image = fns.v1_0.cmd_blit_image; + + if regions.is_empty() { + let min_array_layers = cmp::min(src_image.array_layers(), dst_image.array_layers()); + let region_vk = vk::ImageBlit { + src_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + src_offsets: [[0; 3], src_image.extent()].map(convert_offset), + dst_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..dst_image.subresource_layers() + } + .into(), + dst_offsets: [[0; 3], dst_image.extent()].map(convert_offset), + }; + + unsafe { + cmd_blit_image( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_image.handle(), + dst_image_layout.into(), + 1, + ®ion_vk, + filter.into(), + ) + }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &ImageBlit { + ref src_subresource, + src_offsets, + ref dst_subresource, + dst_offsets, + _ne: _, + } = region; + + vk::ImageBlit { + src_subresource: src_subresource.into(), + src_offsets: src_offsets.map(convert_offset), + dst_subresource: dst_subresource.into(), + dst_offsets: dst_offsets.map(convert_offset), + } + }) + .collect::>(); + + unsafe { + cmd_blit_image( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_image.handle(), + dst_image_layout.into(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + filter.into(), + ) + }; + } + } + + self + } + + /// Resolves a multisampled image into a single-sampled image. + pub unsafe fn resolve_image( + &mut self, + resolve_image_info: &ResolveImageInfo<'_>, + ) -> Result<&mut Self> { + Ok(unsafe { self.resolve_image_unchecked(resolve_image_info) }) + } + + pub unsafe fn resolve_image_unchecked( + &mut self, + resolve_image_info: &ResolveImageInfo<'_>, + ) -> &mut Self { + let &ResolveImageInfo { + src_image, + src_image_layout, + dst_image, + dst_image_layout, + regions, + _ne: _, + } = resolve_image_info; + + let src_image = unsafe { self.accesses.image_unchecked(src_image) }; + let src_image_layout = AccessType::ResolveTransferRead.image_layout(src_image_layout); + let dst_image = unsafe { self.accesses.image_unchecked(dst_image) }; + let dst_image_layout = AccessType::ResolveTransferRead.image_layout(dst_image_layout); + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let cmd_resolve_image2 = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_resolve_image2 + } else { + fns.khr_copy_commands2.cmd_resolve_image2_khr + }; + + if regions.is_empty() { + let min_array_layers = cmp::min(src_image.array_layers(), dst_image.array_layers()); + let src_extent = src_image.extent(); + let dst_extent = dst_image.extent(); + let regions_vk = [vk::ImageResolve2::default() + .src_subresource( + ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + ) + .src_offset(convert_offset([0; 3])) + .dst_subresource( + ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + ) + .dst_offset(convert_offset([0; 3])) + .extent(convert_extent([ + cmp::min(src_extent[0], dst_extent[0]), + cmp::min(src_extent[1], dst_extent[1]), + cmp::min(src_extent[2], dst_extent[2]), + ]))]; + + let resolve_image_info_vk = vk::ResolveImageInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk); + + unsafe { cmd_resolve_image2(self.handle(), &resolve_image_info_vk) }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &ImageResolve { + ref src_subresource, + src_offset, + ref dst_subresource, + dst_offset, + extent, + _ne: _, + } = region; + + vk::ImageResolve2::default() + .src_subresource(src_subresource.into()) + .src_offset(convert_offset(src_offset)) + .dst_subresource(dst_subresource.into()) + .dst_offset(convert_offset(dst_offset)) + .extent(convert_extent(extent)) + }) + .collect::>(); + + let resolve_image_info_vk = vk::ResolveImageInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk); + + unsafe { cmd_resolve_image2(self.handle(), &resolve_image_info_vk) }; + } + } else { + let cmd_resolve_image = fns.v1_0.cmd_resolve_image; + + if regions.is_empty() { + let min_array_layers = cmp::min(src_image.array_layers(), dst_image.array_layers()); + let src_extent = src_image.extent(); + let dst_extent = dst_image.extent(); + let regions_vk = [vk::ImageResolve { + src_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + src_offset: convert_offset([0; 3]), + dst_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..dst_image.subresource_layers() + } + .into(), + dst_offset: convert_offset([0; 3]), + extent: convert_extent([ + cmp::min(src_extent[0], dst_extent[0]), + cmp::min(src_extent[1], dst_extent[1]), + cmp::min(src_extent[2], dst_extent[2]), + ]), + }]; + + unsafe { + cmd_resolve_image( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_image.handle(), + dst_image_layout.into(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &ImageResolve { + ref src_subresource, + src_offset, + ref dst_subresource, + dst_offset, + extent, + _ne: _, + } = region; + + vk::ImageResolve { + src_subresource: src_subresource.into(), + src_offset: convert_offset(src_offset), + dst_subresource: dst_subresource.into(), + dst_offset: convert_offset(dst_offset), + extent: convert_extent(extent), + } + }) + .collect::>(); + + unsafe { + cmd_resolve_image( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_image.handle(), + dst_image_layout.into(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } + } + + self + } +} + +/// Parameters to copy data from a buffer to another buffer. +/// +/// The fields of `regions` represent bytes. +#[derive(Clone, Debug)] +pub struct CopyBufferInfo<'a> { + /// The buffer to copy from. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub src_buffer: Id, + + /// The buffer to copy to. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub dst_buffer: Id, + + /// The regions of both buffers to copy between, specified in bytes. + /// + /// The default value is a single region, with zero offsets and a `size` equal to the smallest + /// of the two buffers. + pub regions: &'a [BufferCopy<'a>], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for CopyBufferInfo<'_> { + #[inline] + fn default() -> Self { + CopyBufferInfo { + src_buffer: Id::INVALID, + dst_buffer: Id::INVALID, + regions: &[], + _ne: crate::NE, + } + } +} + +/// A region of data to copy between buffers. +#[derive(Clone, Debug)] +pub struct BufferCopy<'a> { + /// The offset in bytes or elements from the start of `src_buffer` that copying will start + /// from. + /// + /// The default value is `0`. + pub src_offset: DeviceSize, + + /// The offset in bytes or elements from the start of `dst_buffer` that copying will start + /// from. + /// + /// The default value is `0`. + pub dst_offset: DeviceSize, + + /// The number of bytes or elements to copy. + /// + /// The default value is `0`, which must be overridden. + pub size: DeviceSize, + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for BufferCopy<'_> { + #[inline] + fn default() -> Self { + Self { + src_offset: 0, + dst_offset: 0, + size: 0, + _ne: crate::NE, + } + } +} + +/// Parameters to copy data from an image to another image. +#[derive(Clone, Debug)] +pub struct CopyImageInfo<'a> { + /// The image to copy from. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub src_image: Id, + + /// The layout used for `src_image` during the copy operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub src_image_layout: ImageLayoutType, + + /// The image to copy to. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub dst_image: Id, + + /// The layout used for `dst_image` during the copy operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub dst_image_layout: ImageLayoutType, + + /// The regions of both images to copy between. + /// + /// The default value is a single region, covering the first mip level, and the smallest of the + /// array layers and extent of the two images. All aspects of each image are selected, or + /// `plane0` if the image is multi-planar. + pub regions: &'a [ImageCopy<'a>], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for CopyImageInfo<'_> { + #[inline] + fn default() -> Self { + CopyImageInfo { + src_image: Id::INVALID, + src_image_layout: ImageLayoutType::Optimal, + dst_image: Id::INVALID, + dst_image_layout: ImageLayoutType::Optimal, + regions: &[], + _ne: crate::NE, + } + } +} + +/// A region of data to copy between images. +#[derive(Clone, Debug)] +pub struct ImageCopy<'a> { + /// The subresource of `src_image` to copy from. + /// + /// The default value is empty, which must be overridden. + pub src_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of `src_image` that copying will start from. + /// + /// The default value is `[0; 3]`. + pub src_offset: [u32; 3], + + /// The subresource of `dst_image` to copy to. + /// + /// The default value is empty, which must be overridden. + pub dst_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of `dst_image` that copying will start from. + /// + /// The default value is `[0; 3]`. + pub dst_offset: [u32; 3], + + /// The extent of texels to copy. + /// + /// The default value is `[0; 3]`, which must be overridden. + pub extent: [u32; 3], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for ImageCopy<'_> { + #[inline] + fn default() -> Self { + Self { + src_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + src_offset: [0; 3], + dst_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + dst_offset: [0; 3], + extent: [0; 3], + _ne: crate::NE, + } + } +} + +/// Parameters to copy data from a buffer to an image. +#[derive(Clone, Debug)] +pub struct CopyBufferToImageInfo<'a> { + /// The buffer to copy from. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub src_buffer: Id, + + /// The image to copy to. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub dst_image: Id, + + /// The layout used for `dst_image` during the copy operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub dst_image_layout: ImageLayoutType, + + /// The regions of the buffer and image to copy between. + /// + /// The default value is a single region, covering all of the buffer and the first mip level of + /// the image. All aspects of the image are selected, or `plane0` if the image is multi-planar. + pub regions: &'a [BufferImageCopy<'a>], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for CopyBufferToImageInfo<'_> { + #[inline] + fn default() -> Self { + CopyBufferToImageInfo { + src_buffer: Id::INVALID, + dst_image: Id::INVALID, + dst_image_layout: ImageLayoutType::Optimal, + regions: &[], + _ne: crate::NE, + } + } +} + +/// Parameters to copy data from an image to a buffer. +#[derive(Clone, Debug)] +pub struct CopyImageToBufferInfo<'a> { + /// The image to copy from. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub src_image: Id, + + /// The layout used for `src_image` during the copy operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub src_image_layout: ImageLayoutType, + + /// The buffer to copy to. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub dst_buffer: Id, + + /// The regions of the image and buffer to copy between. + /// + /// The default value is a single region, covering all of the buffer and the first mip level of + /// the image. All aspects of the image are selected, or `plane0` if the image is multi-planar. + pub regions: &'a [BufferImageCopy<'a>], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for CopyImageToBufferInfo<'_> { + #[inline] + fn default() -> Self { + CopyImageToBufferInfo { + src_image: Id::INVALID, + src_image_layout: ImageLayoutType::Optimal, + dst_buffer: Id::INVALID, + regions: &[], + _ne: crate::NE, + } + } +} + +/// A region of data to copy between a buffer and an image. +#[derive(Clone, Debug)] +pub struct BufferImageCopy<'a> { + /// The offset in bytes from the start of the buffer that copying will start from. + /// + /// The default value is `0`. + pub buffer_offset: DeviceSize, + + /// The number of texels between successive rows of image data in the buffer. + /// + /// If set to `0`, the width of the image is used. + /// + /// The default value is `0`. + pub buffer_row_length: u32, + + /// The number of rows between successive depth slices of image data in the buffer. + /// + /// If set to `0`, the height of the image is used. + /// + /// The default value is `0`. + pub buffer_image_height: u32, + + /// The subresource of the image to copy from/to. + /// + /// The default value is empty, which must be overridden. + pub image_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of the image that copying will start from. + /// + /// The default value is `[0; 3]`. + pub image_offset: [u32; 3], + + /// The extent of texels in the image to copy. + /// + /// The default value is `[0; 3]`, which must be overridden. + pub image_extent: [u32; 3], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for BufferImageCopy<'_> { + #[inline] + fn default() -> Self { + Self { + buffer_offset: 0, + buffer_row_length: 0, + buffer_image_height: 0, + image_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + image_offset: [0; 3], + image_extent: [0; 3], + _ne: crate::NE, + } + } +} + +/// Parameters to blit image data. +#[derive(Clone, Debug)] +pub struct BlitImageInfo<'a> { + /// The image to blit from. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub src_image: Id, + + /// The layout used for `src_image` during the blit operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub src_image_layout: ImageLayoutType, + + /// The image to blit to. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub dst_image: Id, + + /// The layout used for `dst_image` during the blit operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub dst_image_layout: ImageLayoutType, + + /// The regions of both images to blit between. + /// + /// The default value is a single region, covering the first mip level, and the smallest of the + /// array layers of the two images. The whole extent of each image is covered, scaling if + /// necessary. All aspects of each image are selected, or `plane0` if the image is + /// multi-planar. + pub regions: &'a [ImageBlit<'a>], + + /// The filter to use for sampling `src_image` when the `src_extent` and + /// `dst_extent` of a region are not the same size. + /// + /// The default value is [`Filter::Nearest`]. + pub filter: Filter, + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for BlitImageInfo<'_> { + #[inline] + fn default() -> Self { + BlitImageInfo { + src_image: Id::INVALID, + src_image_layout: ImageLayoutType::Optimal, + dst_image: Id::INVALID, + dst_image_layout: ImageLayoutType::Optimal, + regions: &[], + filter: Filter::Nearest, + _ne: crate::NE, + } + } +} + +/// A region of data to blit between images. +#[derive(Clone, Debug)] +pub struct ImageBlit<'a> { + /// The subresource of `src_image` to blit from. + /// + /// The default value is empty, which must be overridden. + pub src_subresource: ImageSubresourceLayers, + + /// The offsets from the zero coordinate of `src_image`, defining two corners of the region + /// to blit from. + /// If the ordering of the two offsets differs between source and destination, the image will + /// be flipped. + /// + /// The default value is `[[0; 3]; 2]`, which must be overridden. + pub src_offsets: [[u32; 3]; 2], + + /// The subresource of `dst_image` to blit to. + /// + /// The default value is empty, which must be overridden. + pub dst_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of `dst_image` defining two corners of the + /// region to blit to. + /// If the ordering of the two offsets differs between source and destination, the image will + /// be flipped. + /// + /// The default value is `[[0; 3]; 2]`, which must be overridden. + pub dst_offsets: [[u32; 3]; 2], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for ImageBlit<'_> { + #[inline] + fn default() -> Self { + Self { + src_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + src_offsets: [[0; 3]; 2], + dst_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + dst_offsets: [[0; 3]; 2], + _ne: crate::NE, + } + } +} + +/// Parameters to resolve image data. +#[derive(Clone, Debug)] +pub struct ResolveImageInfo<'a> { + /// The multisampled image to resolve from. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub src_image: Id, + + /// The layout used for `src_image` during the resolve operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub src_image_layout: ImageLayoutType, + + /// The non-multisampled image to resolve into. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub dst_image: Id, + + /// The layout used for `dst_image` during the resolve operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub dst_image_layout: ImageLayoutType, + + /// The regions of both images to resolve between. + /// + /// The default value is a single region, covering the first mip level, and the smallest of the + /// array layers and extent of the two images. All aspects of each image are selected, or + /// `plane0` if the image is multi-planar. + pub regions: &'a [ImageResolve<'a>], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for ResolveImageInfo<'_> { + #[inline] + fn default() -> Self { + ResolveImageInfo { + src_image: Id::INVALID, + src_image_layout: ImageLayoutType::Optimal, + dst_image: Id::INVALID, + dst_image_layout: ImageLayoutType::Optimal, + regions: &[], + _ne: crate::NE, + } + } +} + +/// A region of data to resolve between images. +#[derive(Clone, Debug)] +pub struct ImageResolve<'a> { + /// The subresource of `src_image` to resolve from. + /// + /// The default value is empty, which must be overridden. + pub src_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of `src_image` that resolving will start from. + /// + /// The default value is `[0; 3]`. + pub src_offset: [u32; 3], + + /// The subresource of `dst_image` to resolve into. + /// + /// The default value is empty, which must be overridden. + pub dst_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of `dst_image` that resolving will start from. + /// + /// The default value is `[0; 3]`. + pub dst_offset: [u32; 3], + + /// The extent of texels to resolve. + /// + /// The default value is `[0; 3]`, which must be overridden. + pub extent: [u32; 3], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for ImageResolve<'_> { + #[inline] + fn default() -> Self { + Self { + src_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + src_offset: [0; 3], + dst_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + dst_offset: [0; 3], + extent: [0; 3], + _ne: crate::NE, + } + } +} + +fn convert_offset(offset: [u32; 3]) -> vk::Offset3D { + vk::Offset3D { + x: offset[0] as i32, + y: offset[1] as i32, + z: offset[2] as i32, + } +} + +fn convert_extent(extent: [u32; 3]) -> vk::Extent3D { + vk::Extent3D { + width: extent[0], + height: extent[1], + depth: extent[2], + } +} diff --git a/vulkano-taskgraph/src/command_buffer/commands/dynamic_state.rs b/vulkano-taskgraph/src/command_buffer/commands/dynamic_state.rs new file mode 100644 index 0000000000..6c3eb4bd98 --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/commands/dynamic_state.rs @@ -0,0 +1,781 @@ +use crate::command_buffer::{RecordingCommandBuffer, Result}; +use ash::vk; +use smallvec::SmallVec; +use std::ops::RangeInclusive; +use vulkano::{ + device::DeviceOwned, + pipeline::graphics::{ + color_blend::LogicOp, + conservative_rasterization::ConservativeRasterizationMode, + depth_stencil::{CompareOp, StencilFaces, StencilOp}, + input_assembly::PrimitiveTopology, + rasterization::{CullMode, FrontFace}, + vertex_input::{ + VertexInputAttributeDescription, VertexInputBindingDescription, VertexInputRate, + VertexInputState, + }, + viewport::{Scissor, Viewport}, + }, + Version, VulkanObject, +}; + +/// # Commands to set dynamic state for pipelines +/// +/// These commands require a queue with a pipeline type that uses the given state. +impl RecordingCommandBuffer<'_> { + /// Sets the dynamic blend constants for future draw calls. + pub unsafe fn set_blend_constants(&mut self, constants: &[f32; 4]) -> Result<&mut Self> { + Ok(unsafe { self.set_blend_constants_unchecked(constants) }) + } + + pub unsafe fn set_blend_constants_unchecked(&mut self, constants: &[f32; 4]) -> &mut Self { + let fns = self.device().fns(); + unsafe { (fns.v1_0.cmd_set_blend_constants)(self.handle(), constants) }; + + self + } + + /// Sets whether dynamic color writes should be enabled for each attachment in the framebuffer. + pub unsafe fn set_color_write_enable(&mut self, enables: &[bool]) -> Result<&mut Self> { + Ok(unsafe { self.set_color_write_enable_unchecked(enables) }) + } + + pub unsafe fn set_color_write_enable_unchecked(&mut self, enables: &[bool]) -> &mut Self { + if enables.is_empty() { + return self; + } + + let enables_vk = enables + .iter() + .copied() + .map(|v| v as vk::Bool32) + .collect::>(); + + let fns = self.device().fns(); + unsafe { + (fns.ext_color_write_enable.cmd_set_color_write_enable_ext)( + self.handle(), + enables_vk.len() as u32, + enables_vk.as_ptr(), + ) + }; + + self + } + + /// Sets the dynamic cull mode for future draw calls. + pub unsafe fn set_cull_mode(&mut self, cull_mode: CullMode) -> Result<&mut Self> { + Ok(unsafe { self.set_cull_mode_unchecked(cull_mode) }) + } + + pub unsafe fn set_cull_mode_unchecked(&mut self, cull_mode: CullMode) -> &mut Self { + let fns = self.device().fns(); + let cmd_set_cull_mode = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_set_cull_mode + } else { + fns.ext_extended_dynamic_state.cmd_set_cull_mode_ext + }; + + unsafe { cmd_set_cull_mode(self.handle(), cull_mode.into()) }; + + self + } + + /// Sets the dynamic depth bias values for future draw calls. + pub unsafe fn set_depth_bias( + &mut self, + constant_factor: f32, + clamp: f32, + slope_factor: f32, + ) -> Result<&mut Self> { + Ok(unsafe { self.set_depth_bias_unchecked(constant_factor, clamp, slope_factor) }) + } + + pub unsafe fn set_depth_bias_unchecked( + &mut self, + constant_factor: f32, + clamp: f32, + slope_factor: f32, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_set_depth_bias)(self.handle(), constant_factor, clamp, slope_factor) + }; + + self + } + + /// Sets whether dynamic depth bias is enabled for future draw calls. + pub unsafe fn set_depth_bias_enable(&mut self, enable: bool) -> Result<&mut Self> { + Ok(unsafe { self.set_depth_bias_enable_unchecked(enable) }) + } + + pub unsafe fn set_depth_bias_enable_unchecked(&mut self, enable: bool) -> &mut Self { + let fns = self.device().fns(); + let cmd_set_depth_bias_enable = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_set_depth_bias_enable + } else { + fns.ext_extended_dynamic_state2 + .cmd_set_depth_bias_enable_ext + }; + + unsafe { cmd_set_depth_bias_enable(self.handle(), enable.into()) }; + + self + } + + /// Sets the dynamic depth bounds for future draw calls. + pub unsafe fn set_depth_bounds(&mut self, bounds: RangeInclusive) -> Result<&mut Self> { + Ok(unsafe { self.set_depth_bounds_unchecked(bounds.clone()) }) + } + + pub unsafe fn set_depth_bounds_unchecked(&mut self, bounds: RangeInclusive) -> &mut Self { + let fns = self.device().fns(); + unsafe { (fns.v1_0.cmd_set_depth_bounds)(self.handle(), *bounds.start(), *bounds.end()) }; + + self + } + + /// Sets whether dynamic depth bounds testing is enabled for future draw calls. + pub unsafe fn set_depth_bounds_test_enable(&mut self, enable: bool) -> Result<&mut Self> { + Ok(unsafe { self.set_depth_bounds_test_enable_unchecked(enable) }) + } + + pub unsafe fn set_depth_bounds_test_enable_unchecked(&mut self, enable: bool) -> &mut Self { + let fns = self.device().fns(); + let cmd_set_depth_bounds_test_enable = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_set_depth_bounds_test_enable + } else { + fns.ext_extended_dynamic_state + .cmd_set_depth_bounds_test_enable_ext + }; + + unsafe { cmd_set_depth_bounds_test_enable(self.handle(), enable.into()) }; + + self + } + + /// Sets the dynamic depth compare op for future draw calls. + pub unsafe fn set_depth_compare_op(&mut self, compare_op: CompareOp) -> Result<&mut Self> { + Ok(unsafe { self.set_depth_compare_op_unchecked(compare_op) }) + } + + pub unsafe fn set_depth_compare_op_unchecked(&mut self, compare_op: CompareOp) -> &mut Self { + let fns = self.device().fns(); + let cmd_set_depth_compare_op = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_set_depth_compare_op + } else { + fns.ext_extended_dynamic_state.cmd_set_depth_compare_op_ext + }; + + unsafe { cmd_set_depth_compare_op(self.handle(), compare_op.into()) }; + + self + } + + /// Sets whether dynamic depth testing is enabled for future draw calls. + pub unsafe fn set_depth_test_enable(&mut self, enable: bool) -> Result<&mut Self> { + Ok(unsafe { self.set_depth_test_enable_unchecked(enable) }) + } + + pub unsafe fn set_depth_test_enable_unchecked(&mut self, enable: bool) -> &mut Self { + let fns = self.device().fns(); + let cmd_set_depth_test_enable = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_set_depth_test_enable + } else { + fns.ext_extended_dynamic_state.cmd_set_depth_test_enable_ext + }; + + unsafe { cmd_set_depth_test_enable(self.handle(), enable.into()) }; + + self + } + + /// Sets whether dynamic depth write is enabled for future draw calls. + pub unsafe fn set_depth_write_enable(&mut self, enable: bool) -> Result<&mut Self> { + Ok(unsafe { self.set_depth_write_enable_unchecked(enable) }) + } + + pub unsafe fn set_depth_write_enable_unchecked(&mut self, enable: bool) -> &mut Self { + let fns = self.device().fns(); + let cmd_set_depth_write_enable = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_set_depth_write_enable + } else { + fns.ext_extended_dynamic_state + .cmd_set_depth_write_enable_ext + }; + + unsafe { cmd_set_depth_write_enable(self.handle(), enable.into()) }; + + self + } + + /// Sets the dynamic discard rectangles for future draw calls. + pub unsafe fn set_discard_rectangle( + &mut self, + first_rectangle: u32, + rectangles: &[Scissor], + ) -> Result<&mut Self> { + Ok(unsafe { self.set_discard_rectangle_unchecked(first_rectangle, rectangles) }) + } + + pub unsafe fn set_discard_rectangle_unchecked( + &mut self, + first_rectangle: u32, + rectangles: &[Scissor], + ) -> &mut Self { + if rectangles.is_empty() { + return self; + } + + let rectangles_vk = rectangles + .iter() + .map(|v| v.into()) + .collect::>(); + + let fns = self.device().fns(); + unsafe { + (fns.ext_discard_rectangles.cmd_set_discard_rectangle_ext)( + self.handle(), + first_rectangle, + rectangles_vk.len() as u32, + rectangles_vk.as_ptr(), + ) + }; + + self + } + + /// Sets the dynamic front face for future draw calls. + pub unsafe fn set_front_face(&mut self, face: FrontFace) -> Result<&mut Self> { + Ok(unsafe { self.set_front_face_unchecked(face) }) + } + + pub unsafe fn set_front_face_unchecked(&mut self, face: FrontFace) -> &mut Self { + let fns = self.device().fns(); + let cmd_set_front_face = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_set_front_face + } else { + fns.ext_extended_dynamic_state.cmd_set_front_face_ext + }; + + unsafe { cmd_set_front_face(self.handle(), face.into()) }; + + self + } + + /// Sets the dynamic line stipple values for future draw calls. + pub unsafe fn set_line_stipple(&mut self, factor: u32, pattern: u16) -> Result<&mut Self> { + Ok(unsafe { self.set_line_stipple_unchecked(factor, pattern) }) + } + + pub unsafe fn set_line_stipple_unchecked(&mut self, factor: u32, pattern: u16) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.ext_line_rasterization.cmd_set_line_stipple_ext)(self.handle(), factor, pattern) + }; + + self + } + + /// Sets the dynamic line width for future draw calls. + pub unsafe fn set_line_width(&mut self, line_width: f32) -> Result<&mut Self> { + Ok(unsafe { self.set_line_width_unchecked(line_width) }) + } + + pub unsafe fn set_line_width_unchecked(&mut self, line_width: f32) -> &mut Self { + let fns = self.device().fns(); + unsafe { (fns.v1_0.cmd_set_line_width)(self.handle(), line_width) }; + + self + } + + /// Sets the dynamic logic op for future draw calls. + pub unsafe fn set_logic_op(&mut self, logic_op: LogicOp) -> Result<&mut Self> { + Ok(unsafe { self.set_logic_op_unchecked(logic_op) }) + } + + pub unsafe fn set_logic_op_unchecked(&mut self, logic_op: LogicOp) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.ext_extended_dynamic_state2.cmd_set_logic_op_ext)(self.handle(), logic_op.into()) + }; + + self + } + + /// Sets the dynamic number of patch control points for future draw calls. + pub unsafe fn set_patch_control_points(&mut self, num: u32) -> Result<&mut Self> { + Ok(unsafe { self.set_patch_control_points_unchecked(num) }) + } + + pub unsafe fn set_patch_control_points_unchecked(&mut self, num: u32) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.ext_extended_dynamic_state2 + .cmd_set_patch_control_points_ext)(self.handle(), num) + }; + + self + } + + /// Sets whether dynamic primitive restart is enabled for future draw calls. + pub unsafe fn set_primitive_restart_enable(&mut self, enable: bool) -> Result<&mut Self> { + Ok(unsafe { self.set_primitive_restart_enable_unchecked(enable) }) + } + + pub unsafe fn set_primitive_restart_enable_unchecked(&mut self, enable: bool) -> &mut Self { + let fns = self.device().fns(); + let cmd_set_primitive_restart_enable = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_set_primitive_restart_enable + } else { + fns.ext_extended_dynamic_state2 + .cmd_set_primitive_restart_enable_ext + }; + + unsafe { cmd_set_primitive_restart_enable(self.handle(), enable.into()) }; + + self + } + + /// Sets the dynamic primitive topology for future draw calls. + pub unsafe fn set_primitive_topology( + &mut self, + topology: PrimitiveTopology, + ) -> Result<&mut Self> { + Ok(unsafe { self.set_primitive_topology_unchecked(topology) }) + } + + pub unsafe fn set_primitive_topology_unchecked( + &mut self, + topology: PrimitiveTopology, + ) -> &mut Self { + let fns = self.device().fns(); + let cmd_set_primitive_topology = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_set_primitive_topology + } else { + fns.ext_extended_dynamic_state + .cmd_set_primitive_topology_ext + }; + + unsafe { cmd_set_primitive_topology(self.handle(), topology.into()) }; + + self + } + + /// Sets whether dynamic rasterizer discard is enabled for future draw calls. + pub unsafe fn set_rasterizer_discard_enable(&mut self, enable: bool) -> Result<&mut Self> { + Ok(unsafe { self.set_rasterizer_discard_enable_unchecked(enable) }) + } + + pub unsafe fn set_rasterizer_discard_enable_unchecked(&mut self, enable: bool) -> &mut Self { + let fns = self.device().fns(); + let cmd_set_rasterizer_discard_enable = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_set_rasterizer_discard_enable + } else { + fns.ext_extended_dynamic_state2 + .cmd_set_rasterizer_discard_enable_ext + }; + + unsafe { cmd_set_rasterizer_discard_enable(self.handle(), enable.into()) }; + + self + } + + /// Sets the dynamic scissors for future draw calls. + pub unsafe fn set_scissor( + &mut self, + first_scissor: u32, + scissors: &[Scissor], + ) -> Result<&mut Self> { + Ok(unsafe { self.set_scissor_unchecked(first_scissor, scissors) }) + } + + pub unsafe fn set_scissor_unchecked( + &mut self, + first_scissor: u32, + scissors: &[Scissor], + ) -> &mut Self { + if scissors.is_empty() { + return self; + } + + let scissors_vk = scissors + .iter() + .map(vk::Rect2D::from) + .collect::>(); + + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_set_scissor)( + self.handle(), + first_scissor, + scissors_vk.len() as u32, + scissors_vk.as_ptr(), + ) + }; + + self + } + + /// Sets the dynamic scissors with count for future draw calls. + pub unsafe fn set_scissor_with_count(&mut self, scissors: &[Scissor]) -> Result<&mut Self> { + Ok(unsafe { self.set_scissor_with_count_unchecked(scissors) }) + } + + pub unsafe fn set_scissor_with_count_unchecked(&mut self, scissors: &[Scissor]) -> &mut Self { + if scissors.is_empty() { + return self; + } + + let scissors_vk = scissors + .iter() + .map(vk::Rect2D::from) + .collect::>(); + + let fns = self.device().fns(); + let cmd_set_scissor_with_count = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_set_scissor_with_count + } else { + fns.ext_extended_dynamic_state + .cmd_set_scissor_with_count_ext + }; + + unsafe { + cmd_set_scissor_with_count( + self.handle(), + scissors_vk.len() as u32, + scissors_vk.as_ptr(), + ) + }; + + self + } + + /// Sets the dynamic stencil compare mask on one or both faces for future draw calls. + pub unsafe fn set_stencil_compare_mask( + &mut self, + faces: StencilFaces, + compare_mask: u32, + ) -> Result<&mut Self> { + Ok(unsafe { self.set_stencil_compare_mask_unchecked(faces, compare_mask) }) + } + + pub unsafe fn set_stencil_compare_mask_unchecked( + &mut self, + faces: StencilFaces, + compare_mask: u32, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_set_stencil_compare_mask)(self.handle(), faces.into(), compare_mask) + }; + + self + } + + /// Sets the dynamic stencil ops on one or both faces for future draw calls. + pub unsafe fn set_stencil_op( + &mut self, + faces: StencilFaces, + fail_op: StencilOp, + pass_op: StencilOp, + depth_fail_op: StencilOp, + compare_op: CompareOp, + ) -> Result<&mut Self> { + Ok(unsafe { + self.set_stencil_op_unchecked(faces, fail_op, pass_op, depth_fail_op, compare_op) + }) + } + + pub unsafe fn set_stencil_op_unchecked( + &mut self, + faces: StencilFaces, + fail_op: StencilOp, + pass_op: StencilOp, + depth_fail_op: StencilOp, + compare_op: CompareOp, + ) -> &mut Self { + let fns = self.device().fns(); + let cmd_set_stencil_op = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_set_stencil_op + } else { + fns.ext_extended_dynamic_state.cmd_set_stencil_op_ext + }; + + unsafe { + cmd_set_stencil_op( + self.handle(), + faces.into(), + fail_op.into(), + pass_op.into(), + depth_fail_op.into(), + compare_op.into(), + ) + }; + + self + } + + /// Sets the dynamic stencil reference on one or both faces for future draw calls. + pub unsafe fn set_stencil_reference( + &mut self, + faces: StencilFaces, + reference: u32, + ) -> Result<&mut Self> { + Ok(unsafe { self.set_stencil_reference_unchecked(faces, reference) }) + } + + pub unsafe fn set_stencil_reference_unchecked( + &mut self, + faces: StencilFaces, + reference: u32, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { (fns.v1_0.cmd_set_stencil_reference)(self.handle(), faces.into(), reference) }; + + self + } + + /// Sets whether dynamic stencil testing is enabled for future draw calls. + pub unsafe fn set_stencil_test_enable(&mut self, enable: bool) -> Result<&mut Self> { + Ok(unsafe { self.set_stencil_test_enable_unchecked(enable) }) + } + + pub unsafe fn set_stencil_test_enable_unchecked(&mut self, enable: bool) -> &mut Self { + let fns = self.device().fns(); + let cmd_set_stencil_test_enable = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_set_stencil_test_enable + } else { + fns.ext_extended_dynamic_state + .cmd_set_stencil_test_enable_ext + }; + + unsafe { cmd_set_stencil_test_enable(self.handle(), enable.into()) }; + + self + } + + /// Sets the dynamic stencil write mask on one or both faces for future draw calls. + pub unsafe fn set_stencil_write_mask( + &mut self, + faces: StencilFaces, + write_mask: u32, + ) -> Result<&mut Self> { + Ok(unsafe { self.set_stencil_write_mask_unchecked(faces, write_mask) }) + } + + pub unsafe fn set_stencil_write_mask_unchecked( + &mut self, + faces: StencilFaces, + write_mask: u32, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { (fns.v1_0.cmd_set_stencil_write_mask)(self.handle(), faces.into(), write_mask) }; + + self + } + + /// Sets the dynamic vertex input for future draw calls. + pub unsafe fn set_vertex_input( + &mut self, + vertex_input_state: &VertexInputState, + ) -> Result<&mut Self> { + Ok(unsafe { self.set_vertex_input_unchecked(vertex_input_state) }) + } + + pub unsafe fn set_vertex_input_unchecked( + &mut self, + vertex_input_state: &VertexInputState, + ) -> &mut Self { + let mut vertex_binding_descriptions_vk: SmallVec<[_; 8]> = SmallVec::new(); + let mut vertex_attribute_descriptions_vk: SmallVec<[_; 8]> = SmallVec::new(); + + let VertexInputState { + bindings, + attributes, + _ne: _, + } = vertex_input_state; + + vertex_binding_descriptions_vk.extend(bindings.iter().map(|(&binding, binding_desc)| { + let &VertexInputBindingDescription { + stride, + input_rate, + _ne: _, + } = binding_desc; + + let divisor = match input_rate { + // VUID-VkVertexInputBindingDescription2EXT-divisor-06227 + VertexInputRate::Vertex => 1, + VertexInputRate::Instance { divisor } => divisor, + }; + + vk::VertexInputBindingDescription2EXT { + binding, + stride, + input_rate: input_rate.into(), + divisor, + ..Default::default() + } + })); + + vertex_attribute_descriptions_vk.extend(attributes.iter().map( + |(&location, attribute_desc)| { + let &VertexInputAttributeDescription { + binding, + format, + offset, + _ne: _, + } = attribute_desc; + + vk::VertexInputAttributeDescription2EXT { + location, + binding, + format: format.into(), + offset, + ..Default::default() + } + }, + )); + + let fns = self.device().fns(); + unsafe { + (fns.ext_vertex_input_dynamic_state.cmd_set_vertex_input_ext)( + self.handle(), + vertex_binding_descriptions_vk.len() as u32, + vertex_binding_descriptions_vk.as_ptr(), + vertex_attribute_descriptions_vk.len() as u32, + vertex_attribute_descriptions_vk.as_ptr(), + ) + }; + + self + } + + /// Sets the dynamic viewports for future draw calls. + pub unsafe fn set_viewport( + &mut self, + first_viewport: u32, + viewports: &[Viewport], + ) -> Result<&mut Self> { + Ok(unsafe { self.set_viewport_unchecked(first_viewport, viewports) }) + } + + pub unsafe fn set_viewport_unchecked( + &mut self, + first_viewport: u32, + viewports: &[Viewport], + ) -> &mut Self { + if viewports.is_empty() { + return self; + } + + let viewports_vk = viewports + .iter() + .map(|v| v.into()) + .collect::>(); + + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_set_viewport)( + self.handle(), + first_viewport, + viewports_vk.len() as u32, + viewports_vk.as_ptr(), + ) + }; + + self + } + + /// Sets the dynamic viewports with count for future draw calls. + pub unsafe fn set_viewport_with_count(&mut self, viewports: &[Viewport]) -> Result<&mut Self> { + Ok(unsafe { self.set_viewport_with_count_unchecked(viewports) }) + } + + pub unsafe fn set_viewport_with_count_unchecked( + &mut self, + viewports: &[Viewport], + ) -> &mut Self { + if viewports.is_empty() { + return self; + } + + let viewports_vk = viewports + .iter() + .map(|v| v.into()) + .collect::>(); + + let fns = self.device().fns(); + let cmd_set_viewport_with_count = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_set_viewport_with_count + } else { + fns.ext_extended_dynamic_state + .cmd_set_viewport_with_count_ext + }; + + unsafe { + cmd_set_viewport_with_count( + self.handle(), + viewports_vk.len() as u32, + viewports_vk.as_ptr(), + ) + }; + + self + } + + /// Sets the dynamic conservative rasterization mode for future draw calls. + pub unsafe fn set_conservative_rasterization_mode( + &mut self, + conservative_rasterization_mode: ConservativeRasterizationMode, + ) -> Result<&mut Self> { + Ok(unsafe { + self.set_conservative_rasterization_mode_unchecked(conservative_rasterization_mode) + }) + } + + pub unsafe fn set_conservative_rasterization_mode_unchecked( + &mut self, + conservative_rasterization_mode: ConservativeRasterizationMode, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.ext_extended_dynamic_state3 + .cmd_set_conservative_rasterization_mode_ext)( + self.handle(), + conservative_rasterization_mode.into(), + ) + }; + + self + } + + /// Sets the dynamic extra primitive overestimation size for future draw calls. + pub unsafe fn set_extra_primitive_overestimation_size( + &mut self, + extra_primitive_overestimation_size: f32, + ) -> Result<&mut Self> { + Ok(unsafe { + self.set_extra_primitive_overestimation_size_unchecked( + extra_primitive_overestimation_size, + ) + }) + } + + pub unsafe fn set_extra_primitive_overestimation_size_unchecked( + &mut self, + extra_primitive_overestimation_size: f32, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.ext_extended_dynamic_state3 + .cmd_set_extra_primitive_overestimation_size_ext)( + self.handle(), + extra_primitive_overestimation_size, + ) + }; + + self + } +} diff --git a/vulkano-taskgraph/src/command_buffer/commands/mod.rs b/vulkano-taskgraph/src/command_buffer/commands/mod.rs new file mode 100644 index 0000000000..080117d52a --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/commands/mod.rs @@ -0,0 +1,6 @@ +pub(super) mod bind_push; +pub(super) mod clear; +pub(super) mod copy; +pub(super) mod dynamic_state; +pub(super) mod pipeline; +pub(super) mod sync; diff --git a/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs b/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs new file mode 100644 index 0000000000..e87d112b6c --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs @@ -0,0 +1,661 @@ +use crate::{ + command_buffer::{RecordingCommandBuffer, Result}, + Id, +}; +#[cfg(doc)] +use vulkano::command_buffer::{ + DispatchIndirectCommand, DrawIndexedIndirectCommand, DrawIndirectCommand, + DrawMeshTasksIndirectCommand, +}; +use vulkano::{buffer::Buffer, device::DeviceOwned, DeviceSize, Version, VulkanObject}; + +/// # Commands to execute a bound pipeline +/// +/// Dispatch commands require a compute queue, draw commands require a graphics queue. +impl RecordingCommandBuffer<'_> { + /// Performs a single compute operation using a compute pipeline. + /// + /// A compute pipeline must have been bound using [`bind_pipeline_compute`]. Any resources used + /// by the compute pipeline, such as descriptor sets, must have been set beforehand. + /// + /// # Safety + /// + /// - The general [shader safety requirements] apply. + /// + /// [`bind_pipeline_compute`]: Self::bind_pipeline_compute + /// [shader safety requirements]: vulkano::shader#safety + pub unsafe fn dispatch(&mut self, group_counts: [u32; 3]) -> Result<&mut Self> { + Ok(unsafe { self.dispatch_unchecked(group_counts) }) + } + + pub unsafe fn dispatch_unchecked(&mut self, group_counts: [u32; 3]) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_dispatch)( + self.handle(), + group_counts[0], + group_counts[1], + group_counts[2], + ) + }; + + self + } + + /// Performs a single compute operation using a compute pipeline. One dispatch is performed + /// for the [`DispatchIndirectCommand`] struct that is read from `buffer` starting at `offset`. + /// + /// A compute pipeline must have been bound using [`bind_pipeline_compute`]. Any resources used + /// by the compute pipeline, such as descriptor sets, must have been set beforehand. + /// + /// # Safety + /// + /// - The general [shader safety requirements] apply. + /// - The [safety requirements for `DispatchIndirectCommand`] apply. + /// + /// [`bind_pipeline_compute`]: Self::bind_pipeline_compute + /// [shader safety requirements]: vulkano::shader#safety + /// [safety requirements for `DispatchIndirectCommand`]: DispatchIndirectCommand#safety + pub unsafe fn dispatch_indirect( + &mut self, + buffer: Id, + offset: DeviceSize, + ) -> Result<&mut Self> { + Ok(unsafe { self.dispatch_indirect_unchecked(buffer, offset) }) + } + + pub unsafe fn dispatch_indirect_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + + let fns = self.device().fns(); + unsafe { (fns.v1_0.cmd_dispatch_indirect)(self.handle(), buffer.handle(), offset) }; + + self + } + + /// Performs a single draw operation using a primitive shading graphics pipeline. + /// + /// The parameters specify the first vertex and the number of vertices to draw, and the first + /// instance and number of instances. For non-instanced drawing, specify `instance_count` as 1 + /// and `first_instance` as 0. + /// + /// A primitive shading graphics pipeline must have been bound using + /// [`bind_pipeline_graphics`]. Any resources used by the graphics pipeline, such as descriptor + /// sets, vertex buffers and dynamic state, must have been set beforehand. If the bound + /// graphics pipeline uses vertex buffers, then the provided vertex and instance ranges must be + /// in range of the bound vertex buffers. + /// + /// # Safety + /// + /// - The general [shader safety requirements] apply. + /// + /// [`bind_pipeline_graphics`]: Self::bind_pipeline_graphics + /// [shader safety requirements]: vulkano::shader#safety + pub unsafe fn draw( + &mut self, + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, + ) -> Result<&mut Self> { + Ok(unsafe { + self.draw_unchecked(vertex_count, instance_count, first_vertex, first_instance) + }) + } + + pub unsafe fn draw_unchecked( + &mut self, + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_draw)( + self.handle(), + vertex_count, + instance_count, + first_vertex, + first_instance, + ) + }; + + self + } + + /// Performs multiple draw operations using a primitive shading graphics pipeline. + /// + /// One draw is performed for each [`DrawIndirectCommand`] struct that is read from `buffer` + /// starting at `offset`, with the offset increasing by `stride` bytes for each successive + /// draw. `draw_count` draw commands are performed. The maximum number of draw commands in the + /// buffer is limited by the [`max_draw_indirect_count`] limit. This limit is 1 unless the + /// [`multi_draw_indirect`] feature has been enabled. + /// + /// A primitive shading graphics pipeline must have been bound using + /// [`bind_pipeline_graphics`]. Any resources used by the graphics pipeline, such as descriptor + /// sets, vertex buffers and dynamic state, must have been set beforehand. If the bound + /// graphics pipeline uses vertex buffers, then the vertex and instance ranges of each + /// `DrawIndirectCommand` in the indirect buffer must be in range of the bound vertex buffers. + /// + /// # Safety + /// + /// - The general [shader safety requirements] apply. + /// - The [safety requirements for `DrawIndirectCommand`] apply. + /// + /// [`max_draw_indirect_count`]: vulkano::device::DeviceProperties::max_draw_indirect_count + /// [`multi_draw_indirect`]: vulkano::device::DeviceFeatures::multi_draw_indirect + /// [`bind_pipeline_graphics`]: Self::bind_pipeline_graphics + /// [shader safety requirements]: vulkano::shader#safety + /// [safety requirements for `DrawIndirectCommand`]: DrawIndirectCommand#safety + pub unsafe fn draw_indirect( + &mut self, + buffer: Id, + offset: DeviceSize, + draw_count: u32, + stride: u32, + ) -> Result<&mut Self> { + Ok(unsafe { self.draw_indirect_unchecked(buffer, offset, draw_count, stride) }) + } + + pub unsafe fn draw_indirect_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + draw_count: u32, + stride: u32, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_draw_indirect)(self.handle(), buffer.handle(), offset, draw_count, stride) + }; + + self + } + + /// Performs multiple draw operations using a primitive shading graphics pipeline, reading the + /// number of draw operations from a separate buffer. + /// + /// One draw is performed for each [`DrawIndirectCommand`] struct that is read from `buffer` + /// starting at `offset`, with the offset increasing by `stride` bytes for each successive + /// draw. The number of draws to perform is read from `count_buffer` at `count_buffer_offset`, + /// or specified by `max_draw_count`, whichever is lower. This number is limited by the + /// [`max_draw_indirect_count`] limit. + /// + /// A primitive shading graphics pipeline must have been bound using + /// [`bind_pipeline_graphics`]. Any resources used by the graphics pipeline, such as descriptor + /// sets, vertex buffers and dynamic state, must have been set beforehand. If the bound + /// graphics pipeline uses vertex buffers, then the vertex and instance ranges of each + /// `DrawIndirectCommand` in the indirect buffer must be in range of the bound vertex buffers. + /// + /// # Safety + /// + /// - The general [shader safety requirements] apply. + /// - The [safety requirements for `DrawIndirectCommand`] apply. + /// - The count stored in `count_buffer` must not be greater than the + /// [`max_draw_indirect_count`] device limit. + /// - The count stored in `count_buffer` must fall within the range of `buffer` starting at + /// `offset`. + /// + /// [`max_draw_indirect_count`]: vulkano::device::DeviceProperties::max_draw_indirect_count + /// [`bind_pipeline_graphics`]: Self::bind_pipeline_graphics + /// [shader safety requirements]: vulkano::shader#safety + /// [safety requirements for `DrawIndirectCommand`]: DrawIndirectCommand#safety + pub unsafe fn draw_indirect_count( + &mut self, + buffer: Id, + offset: DeviceSize, + count_buffer: Id, + count_buffer_offset: DeviceSize, + max_draw_count: u32, + stride: u32, + ) -> Result<&mut Self> { + Ok(unsafe { + self.draw_indirect_count_unchecked( + buffer, + offset, + count_buffer, + count_buffer_offset, + max_draw_count, + stride, + ) + }) + } + + pub unsafe fn draw_indirect_count_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + count_buffer: Id, + count_buffer_offset: DeviceSize, + max_draw_count: u32, + stride: u32, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + let count_buffer = unsafe { self.accesses.buffer_unchecked(count_buffer) }; + + let device = self.device(); + let fns = device.fns(); + let cmd_draw_indirect_count = if device.api_version() >= Version::V1_2 { + fns.v1_2.cmd_draw_indirect_count + } else if device.enabled_extensions().khr_draw_indirect_count { + fns.khr_draw_indirect_count.cmd_draw_indirect_count_khr + } else if device.enabled_extensions().amd_draw_indirect_count { + fns.amd_draw_indirect_count.cmd_draw_indirect_count_amd + } else { + std::process::abort(); + }; + + unsafe { + cmd_draw_indirect_count( + self.handle(), + buffer.handle(), + offset, + count_buffer.handle(), + count_buffer_offset, + max_draw_count, + stride, + ) + }; + + self + } + + /// Performs a single draw operation using a primitive shading graphics pipeline, using an + /// index buffer. + /// + /// The parameters specify the first index and the number of indices in the index buffer that + /// should be used, and the first instance and number of instances. For non-instanced drawing, + /// specify `instance_count` as 1 and `first_instance` as 0. The `vertex_offset` is a constant + /// value that should be added to each index in the index buffer to produce the final vertex + /// number to be used. + /// + /// An index buffer must have been bound using [`bind_index_buffer`], and the provided index + /// range must be in range of the bound index buffer. + /// + /// A primitive shading graphics pipeline must have been bound using + /// [`bind_pipeline_graphics`]. Any resources used by the graphics pipeline, such as descriptor + /// sets, vertex buffers and dynamic state, must have been set beforehand. If the bound + /// graphics pipeline uses vertex buffers, then the provided instance range must be in range of + /// the bound vertex buffers. The vertex indices in the index buffer must be in range of the + /// bound vertex buffers. + /// + /// # Safety + /// + /// - The general [shader safety requirements] apply. + /// - Every vertex number that is retrieved from the index buffer must fall within the range of + /// the bound vertex-rate vertex buffers. + /// - Every vertex number that is retrieved from the index buffer, if it is not the special + /// primitive restart value, must be no greater than the [`max_draw_indexed_index_value`] + /// device limit. + /// + /// [`bind_index_buffer`]: Self::bind_index_buffer + /// [`bind_pipeline_graphics`]: Self::bind_pipeline_graphics + /// [shader safety requirements]: vulkano::shader#safety + /// [`max_draw_indexed_index_value`]: vulkano::device::DeviceProperties::max_draw_indexed_index_value + pub unsafe fn draw_indexed( + &mut self, + index_count: u32, + instance_count: u32, + first_index: u32, + vertex_offset: i32, + first_instance: u32, + ) -> Result<&mut Self> { + Ok(unsafe { + self.draw_indexed_unchecked( + index_count, + instance_count, + first_index, + vertex_offset, + first_instance, + ) + }) + } + + pub unsafe fn draw_indexed_unchecked( + &mut self, + index_count: u32, + instance_count: u32, + first_index: u32, + vertex_offset: i32, + first_instance: u32, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_draw_indexed)( + self.handle(), + index_count, + instance_count, + first_index, + vertex_offset, + first_instance, + ) + }; + + self + } + + /// Performs multiple draw operations using a primitive shading graphics pipeline, using an + /// index buffer. + /// + /// One draw is performed for each [`DrawIndexedIndirectCommand`] struct that is read from + /// `buffer` starting at `offset`, with the offset increasing by `stride` bytes with each + /// successive draw. `draw_count` draw commands are performed. The maximum number of draw + /// commands in the buffer is limited by the [`max_draw_indirect_count`] limit. This limit is 1 + /// unless the [`multi_draw_indirect`] feature has been enabled. + /// + /// An index buffer must have been bound using [`bind_index_buffer`], and the index ranges of + /// each `DrawIndexedIndirectCommand` in the indirect buffer must be in range of the bound + /// index buffer. + /// + /// A primitive shading graphics pipeline must have been bound using + /// [`bind_pipeline_graphics`]. Any resources used by the graphics pipeline, such as descriptor + /// sets, vertex buffers and dynamic state, must have been set beforehand. If the bound + /// graphics pipeline uses vertex buffers, then the instance ranges of each + /// `DrawIndexedIndirectCommand` in the indirect buffer must be in range of the bound vertex + /// buffers. + /// + /// # Safety + /// + /// - The general [shader safety requirements] apply. + /// - The [safety requirements for `DrawIndexedIndirectCommand`] apply. + /// + /// [`max_draw_indirect_count`]: vulkano::device::DeviceProperties::max_draw_indirect_count + /// [`multi_draw_indirect`]: vulkano::device::DeviceFeatures::multi_draw_indirect + /// [`bind_index_buffer`]: Self::bind_index_buffer + /// [`bind_pipeline_graphics`]: Self::bind_pipeline_graphics + /// [shader safety requirements]: vulkano::shader#safety + /// [safety requirements for `DrawIndexedIndirectCommand`]: DrawIndexedIndirectCommand#safety + pub unsafe fn draw_indexed_indirect( + &mut self, + buffer: Id, + offset: DeviceSize, + draw_count: u32, + stride: u32, + ) -> Result<&mut Self> { + Ok(unsafe { self.draw_indexed_indirect_unchecked(buffer, offset, draw_count, stride) }) + } + + pub unsafe fn draw_indexed_indirect_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + draw_count: u32, + stride: u32, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_draw_indexed_indirect)( + self.handle(), + buffer.handle(), + offset, + draw_count, + stride, + ) + }; + + self + } + + /// Performs multiple draw operations using a primitive shading graphics pipeline, using an + /// index buffer, and reading the number of draw operations from a separate buffer. + /// + /// One draw is performed for each [`DrawIndexedIndirectCommand`] struct that is read from + /// `buffer` starting at `offset`, with the offset increasing by `stride` bytes for each + /// successive draw. The number of draws to perform is read from `count_buffer` at + /// `count_buffer_offset`, or specified by `max_draw_count`, whichever is lower. This number is + /// limited by the [`max_draw_indirect_count`] limit. + /// + /// An index buffer must have been bound using [`bind_index_buffer`], and the index ranges of + /// each `DrawIndexedIndirectCommand` in the indirect buffer must be in range of the bound + /// index buffer. + /// + /// A primitive shading graphics pipeline must have been bound using + /// [`bind_pipeline_graphics`]. Any resources used by the graphics pipeline, such as descriptor + /// sets, vertex buffers and dynamic state, must have been set beforehand. If the bound + /// graphics pipeline uses vertex buffers, then the instance ranges of each + /// `DrawIndexedIndirectCommand` in the indirect buffer must be in range of the bound vertex + /// buffers. + /// + /// # Safety + /// + /// - The general [shader safety requirements] apply. + /// - The [safety requirements for `DrawIndexedIndirectCommand`] apply. + /// - The count stored in `count_buffer` must not be greater than the + /// [`max_draw_indirect_count`] device limit. + /// - The count stored in `count_buffer` must fall within the range of `buffer`. + /// + /// [`max_draw_indirect_count`]: vulkano::device::DeviceProperties::max_draw_indirect_count + /// [`bind_index_buffer`]: Self::bind_index_buffer + /// [`bind_pipeline_graphics`]: Self::bind_pipeline_graphics + /// [shader safety requirements]: vulkano::shader#safety + /// [safety requirements for `DrawIndexedIndirectCommand`]: DrawIndexedIndirectCommand#safety + pub unsafe fn draw_indexed_indirect_count( + &mut self, + buffer: Id, + offset: DeviceSize, + count_buffer: Id, + count_buffer_offset: DeviceSize, + max_draw_count: u32, + stride: u32, + ) -> Result<&mut Self> { + Ok(unsafe { + self.draw_indexed_indirect_count_unchecked( + buffer, + offset, + count_buffer, + count_buffer_offset, + max_draw_count, + stride, + ) + }) + } + + pub unsafe fn draw_indexed_indirect_count_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + count_buffer: Id, + count_buffer_offset: DeviceSize, + max_draw_count: u32, + stride: u32, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + let count_buffer = unsafe { self.accesses.buffer_unchecked(count_buffer) }; + + let device = self.device(); + let fns = device.fns(); + let cmd_draw_indexed_indirect_count = if device.api_version() >= Version::V1_2 { + fns.v1_2.cmd_draw_indexed_indirect_count + } else if device.enabled_extensions().khr_draw_indirect_count { + fns.khr_draw_indirect_count + .cmd_draw_indexed_indirect_count_khr + } else if device.enabled_extensions().amd_draw_indirect_count { + fns.amd_draw_indirect_count + .cmd_draw_indexed_indirect_count_amd + } else { + std::process::abort(); + }; + + unsafe { + cmd_draw_indexed_indirect_count( + self.handle(), + buffer.handle(), + offset, + count_buffer.handle(), + count_buffer_offset, + max_draw_count, + stride, + ) + }; + + self + } + + /// Perform a single draw operation using a mesh shading graphics pipeline. + /// + /// A mesh shading graphics pipeline must have been bound using [`bind_pipeline_graphics`]. Any + /// resources used by the graphics pipeline, such as descriptor sets and dynamic state, must + /// have been set beforehand. + /// + /// # Safety + /// + /// - The general [shader safety requirements] apply. + /// + /// [`bind_pipeline_graphics`]: Self::bind_pipeline_graphics + /// [shader safety requirements]: vulkano::shader#safety + pub unsafe fn draw_mesh_tasks(&mut self, group_counts: [u32; 3]) -> Result<&mut Self> { + Ok(unsafe { self.draw_mesh_tasks_unchecked(group_counts) }) + } + + pub unsafe fn draw_mesh_tasks_unchecked(&mut self, group_counts: [u32; 3]) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.ext_mesh_shader.cmd_draw_mesh_tasks_ext)( + self.handle(), + group_counts[0], + group_counts[1], + group_counts[2], + ) + }; + + self + } + + /// Perform multiple draw operations using a mesh shading graphics pipeline. + /// + /// One draw is performed for each [`DrawMeshTasksIndirectCommand`] struct that is read from + /// `buffer` starting at `offset`, with the offset increasing by `stride` bytes for each + /// successive draw. `draw_count` draw commands are performed. The maximum number of draw + /// commands in the buffer is limited by the [`max_draw_indirect_count`] limit. This limit is 1 + /// unless the [`multi_draw_indirect`] feature has been enabled. + /// + /// A mesh shading graphics pipeline must have been bound using [`bind_pipeline_graphics`]. Any + /// resources used by the graphics pipeline, such as descriptor sets and dynamic state, must + /// have been set beforehand. + /// + /// # Safety + /// + /// - The general [shader safety requirements] apply. + /// - The [safety requirements for `DrawMeshTasksIndirectCommand`] apply. + /// + /// [`max_draw_indirect_count`]: vulkano::device::DeviceProperties::max_draw_indirect_count + /// [`multi_draw_indirect`]: vulkano::device::DeviceFeatures::multi_draw_indirect + /// [`bind_pipeline_graphics`]: Self::bind_pipeline_graphics + /// [shader safety requirements]: vulkano::shader#safety + /// [safety requirements for `DrawMeshTasksIndirectCommand`]: DrawMeshTasksIndirectCommand#safety + pub unsafe fn draw_mesh_tasks_indirect( + &mut self, + buffer: Id, + offset: DeviceSize, + draw_count: u32, + stride: u32, + ) -> Result<&mut Self> { + Ok(unsafe { self.draw_mesh_tasks_indirect_unchecked(buffer, offset, draw_count, stride) }) + } + + pub unsafe fn draw_mesh_tasks_indirect_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + draw_count: u32, + stride: u32, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + + let fns = self.device().fns(); + unsafe { + (fns.ext_mesh_shader.cmd_draw_mesh_tasks_indirect_ext)( + self.handle(), + buffer.handle(), + offset, + draw_count, + stride, + ) + }; + + self + } + + /// Performs multiple draw operations using a mesh shading graphics pipeline, reading the + /// number of draw operations from a separate buffer. + /// + /// One draw is performed for each [`DrawMeshTasksIndirectCommand`] struct that is read from + /// `buffer` starting at `offset`, with the offset increasing by `stride` bytes after each + /// successive draw. The number of draws to perform is read from `count_buffer`, or specified + /// by `max_draw_count`, whichever is lower. This number is limited by the + /// [`max_draw_indirect_count`] limit. + /// + /// A mesh shading graphics pipeline must have been bound using [`bind_pipeline_graphics`]. Any + /// resources used by the graphics pipeline, such as descriptor sets and dynamic state, must + /// have been set beforehand. + /// + /// # Safety + /// + /// - The general [shader safety requirements] apply. + /// - The [safety requirements for `DrawMeshTasksIndirectCommand`] apply. + /// - The count stored in `count_buffer` must not be greater than the + /// [`max_draw_indirect_count`] device limit. + /// - The count stored in `count_buffer` must fall within the range of `buffer`. + /// + /// [`max_draw_indirect_count`]: vulkano::device::DeviceProperties::max_draw_indirect_count + /// [`bind_pipeline_graphics`]: Self::bind_pipeline_graphics + /// [shader safety requirements]: vulkano::shader#safety + /// [safety requirements for `DrawMeshTasksIndirectCommand`]: DrawMeshTasksIndirectCommand#safety + pub unsafe fn draw_mesh_tasks_indirect_count( + &mut self, + buffer: Id, + offset: DeviceSize, + count_buffer: Id, + count_buffer_offset: DeviceSize, + max_draw_count: u32, + stride: u32, + ) -> Result<&mut Self> { + Ok(unsafe { + self.draw_mesh_tasks_indirect_count_unchecked( + buffer, + offset, + count_buffer, + count_buffer_offset, + max_draw_count, + stride, + ) + }) + } + + pub unsafe fn draw_mesh_tasks_indirect_count_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + count_buffer: Id, + count_buffer_offset: DeviceSize, + max_draw_count: u32, + stride: u32, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + let count_buffer = unsafe { self.accesses.buffer_unchecked(count_buffer) }; + + let fns = self.device().fns(); + unsafe { + (fns.ext_mesh_shader.cmd_draw_mesh_tasks_indirect_count_ext)( + self.handle(), + buffer.handle(), + offset, + count_buffer.handle(), + count_buffer_offset, + max_draw_count, + stride, + ) + }; + + self + } +} diff --git a/vulkano-taskgraph/src/command_buffer/commands/sync.rs b/vulkano-taskgraph/src/command_buffer/commands/sync.rs new file mode 100644 index 0000000000..33f95107de --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/commands/sync.rs @@ -0,0 +1,474 @@ +use crate::{ + command_buffer::{RecordingCommandBuffer, Result}, + Id, +}; +use ash::vk; +use smallvec::SmallVec; +use std::ops::Range; +use vulkano::{ + buffer::Buffer, + device::DeviceOwned, + image::{Image, ImageAspects, ImageLayout, ImageSubresourceRange}, + sync::{AccessFlags, DependencyFlags, PipelineStages}, + DeviceSize, Version, VulkanObject, +}; + +/// # Commands to synchronize resource accesses +impl RecordingCommandBuffer<'_> { + pub unsafe fn pipeline_barrier( + &mut self, + dependency_info: &DependencyInfo<'_>, + ) -> Result<&mut Self> { + Ok(unsafe { self.pipeline_barrier_unchecked(dependency_info) }) + } + + pub unsafe fn pipeline_barrier_unchecked( + &mut self, + dependency_info: &DependencyInfo<'_>, + ) -> &mut Self { + if dependency_info.is_empty() { + return self; + } + + let &DependencyInfo { + dependency_flags, + memory_barriers, + buffer_memory_barriers, + image_memory_barriers, + _ne: _, + } = dependency_info; + + if self.device().enabled_features().synchronization2 { + let memory_barriers_vk: SmallVec<[_; 2]> = memory_barriers + .iter() + .map(|barrier| { + let &MemoryBarrier { + src_stages, + src_access, + dst_stages, + dst_access, + _ne: _, + } = barrier; + + vk::MemoryBarrier2::default() + .src_stage_mask(src_stages.into()) + .src_access_mask(src_access.into()) + .dst_stage_mask(dst_stages.into()) + .dst_access_mask(dst_access.into()) + }) + .collect(); + + let buffer_memory_barriers_vk: SmallVec<[_; 8]> = buffer_memory_barriers + .iter() + .map(|barrier| { + let &BufferMemoryBarrier { + src_stages, + src_access, + dst_stages, + dst_access, + buffer, + ref range, + _ne: _, + } = barrier; + + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + + vk::BufferMemoryBarrier2::default() + .src_stage_mask(src_stages.into()) + .src_access_mask(src_access.into()) + .dst_stage_mask(dst_stages.into()) + .dst_access_mask(dst_access.into()) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .buffer(buffer.handle()) + .offset(range.start) + .size(range.end - range.start) + }) + .collect(); + + let image_memory_barriers_vk: SmallVec<[_; 8]> = image_memory_barriers + .iter() + .map(|barrier| { + let &ImageMemoryBarrier { + src_stages, + src_access, + dst_stages, + dst_access, + old_layout, + new_layout, + image, + ref subresource_range, + _ne: _, + } = barrier; + + let image = unsafe { self.accesses.image_unchecked(image) }; + + vk::ImageMemoryBarrier2::default() + .src_stage_mask(src_stages.into()) + .src_access_mask(src_access.into()) + .dst_stage_mask(dst_stages.into()) + .dst_access_mask(dst_access.into()) + .old_layout(old_layout.into()) + .new_layout(new_layout.into()) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .image(image.handle()) + .subresource_range(subresource_range.clone().into()) + }) + .collect(); + + let dependency_info_vk = vk::DependencyInfo::default() + .dependency_flags(dependency_flags.into()) + .memory_barriers(&memory_barriers_vk) + .buffer_memory_barriers(&buffer_memory_barriers_vk) + .image_memory_barriers(&image_memory_barriers_vk); + + let fns = self.device().fns(); + let cmd_pipeline_barrier2 = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_pipeline_barrier2 + } else { + fns.khr_synchronization2.cmd_pipeline_barrier2_khr + }; + + unsafe { cmd_pipeline_barrier2(self.handle(), &dependency_info_vk) }; + } else { + let mut src_stage_mask = vk::PipelineStageFlags::empty(); + let mut dst_stage_mask = vk::PipelineStageFlags::empty(); + + let memory_barriers_vk: SmallVec<[_; 2]> = memory_barriers + .iter() + .map(|barrier| { + let &MemoryBarrier { + src_stages, + src_access, + dst_stages, + dst_access, + _ne: _, + } = barrier; + + src_stage_mask |= src_stages.into(); + dst_stage_mask |= dst_stages.into(); + + vk::MemoryBarrier::default() + .src_access_mask(src_access.into()) + .dst_access_mask(dst_access.into()) + }) + .collect(); + + let buffer_memory_barriers_vk: SmallVec<[_; 8]> = buffer_memory_barriers + .iter() + .map(|barrier| { + let &BufferMemoryBarrier { + src_stages, + src_access, + dst_stages, + dst_access, + buffer, + ref range, + _ne: _, + } = barrier; + + src_stage_mask |= src_stages.into(); + dst_stage_mask |= dst_stages.into(); + + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + + vk::BufferMemoryBarrier::default() + .src_access_mask(src_access.into()) + .dst_access_mask(dst_access.into()) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .buffer(buffer.handle()) + .offset(range.start) + .size(range.end - range.start) + }) + .collect(); + + let image_memory_barriers_vk: SmallVec<[_; 8]> = image_memory_barriers + .iter() + .map(|barrier| { + let &ImageMemoryBarrier { + src_stages, + src_access, + dst_stages, + dst_access, + old_layout, + new_layout, + image, + ref subresource_range, + _ne: _, + } = barrier; + + src_stage_mask |= src_stages.into(); + dst_stage_mask |= dst_stages.into(); + + let image = unsafe { self.accesses.image_unchecked(image) }; + + vk::ImageMemoryBarrier::default() + .src_access_mask(src_access.into()) + .dst_access_mask(dst_access.into()) + .old_layout(old_layout.into()) + .new_layout(new_layout.into()) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .image(image.handle()) + .subresource_range(subresource_range.clone().into()) + }) + .collect(); + + if src_stage_mask.is_empty() { + // "VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT is [...] equivalent to + // VK_PIPELINE_STAGE_2_NONE in the first scope." + src_stage_mask |= vk::PipelineStageFlags::TOP_OF_PIPE; + } + + if dst_stage_mask.is_empty() { + // "VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT is [...] equivalent to + // VK_PIPELINE_STAGE_2_NONE in the second scope." + dst_stage_mask |= vk::PipelineStageFlags::BOTTOM_OF_PIPE; + } + + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_pipeline_barrier)( + self.handle(), + src_stage_mask, + dst_stage_mask, + dependency_flags.into(), + memory_barriers_vk.len() as u32, + memory_barriers_vk.as_ptr(), + buffer_memory_barriers_vk.len() as u32, + buffer_memory_barriers_vk.as_ptr(), + image_memory_barriers_vk.len() as u32, + image_memory_barriers_vk.as_ptr(), + ) + }; + } + + self + } +} + +/// Dependency info for barriers in a pipeline barrier command. +/// +/// A pipeline barrier creates a dependency between commands submitted before the barrier (the +/// source scope) and commands submitted after it (the destination scope). Each `DependencyInfo` +/// consists of multiple individual barriers that concern either a single resource or operate +/// globally. +/// +/// Each barrier has a set of source/destination pipeline stages and source/destination memory +/// access types. The pipeline stages create an *execution dependency*: the `src_stages` of +/// commands submitted before the barrier must be completely finished before any of the +/// `dst_stages` of commands after the barrier are allowed to start. The memory access types create +/// a *memory dependency*: in addition to the execution dependency, any `src_access` +/// performed before the barrier must be made available and visible before any `dst_access` +/// are made after the barrier. +#[derive(Clone, Debug)] +pub struct DependencyInfo<'a> { + /// Flags to modify how the execution and memory dependencies are formed. + /// + /// The default value is empty. + pub dependency_flags: DependencyFlags, + + /// Memory barriers for global operations and accesses, not limited to a single resource. + /// + /// The default value is empty. + pub memory_barriers: &'a [MemoryBarrier<'a>], + + /// Memory barriers for individual buffers. + /// + /// The default value is empty. + pub buffer_memory_barriers: &'a [BufferMemoryBarrier<'a>], + + /// Memory barriers for individual images. + /// + /// The default value is empty. + pub image_memory_barriers: &'a [ImageMemoryBarrier<'a>], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl DependencyInfo<'_> { + /// Returns `true` if `self` doesn't contain any barriers. + #[inline] + pub fn is_empty(&self) -> bool { + self.memory_barriers.is_empty() + && self.buffer_memory_barriers.is_empty() + && self.image_memory_barriers.is_empty() + } +} + +impl Default for DependencyInfo<'_> { + #[inline] + fn default() -> Self { + DependencyInfo { + dependency_flags: DependencyFlags::default(), + memory_barriers: &[], + buffer_memory_barriers: &[], + image_memory_barriers: &[], + _ne: crate::NE, + } + } +} + +/// A memory barrier that is applied globally. +#[derive(Clone, Debug)] +pub struct MemoryBarrier<'a> { + /// The pipeline stages in the source scope to wait for. + /// + /// The default value is [`PipelineStages::empty()`]. + pub src_stages: PipelineStages, + + /// The memory accesses in the source scope to make available and visible. + /// + /// The default value is [`AccessFlags::empty()`]. + pub src_access: AccessFlags, + + /// The pipeline stages in the destination scope that must wait for `src_stages`. + /// + /// The default value is [`PipelineStages::empty()`]. + pub dst_stages: PipelineStages, + + /// The memory accesses in the destination scope that must wait for `src_access` to be made + /// available and visible. + /// + /// The default value is [`AccessFlags::empty()`]. + pub dst_access: AccessFlags, + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for MemoryBarrier<'_> { + #[inline] + fn default() -> Self { + Self { + src_stages: PipelineStages::empty(), + src_access: AccessFlags::empty(), + dst_stages: PipelineStages::empty(), + dst_access: AccessFlags::empty(), + _ne: crate::NE, + } + } +} + +/// A memory barrier that is applied to a single buffer. +#[derive(Clone, Debug)] +pub struct BufferMemoryBarrier<'a> { + /// The pipeline stages in the source scope to wait for. + /// + /// The default value is [`PipelineStages::empty()`]. + pub src_stages: PipelineStages, + + /// The memory accesses in the source scope to make available and visible. + /// + /// The default value is [`AccessFlags::empty()`]. + pub src_access: AccessFlags, + + /// The pipeline stages in the destination scope that must wait for `src_stages`. + /// + /// The default value is [`PipelineStages::empty()`]. + pub dst_stages: PipelineStages, + + /// The memory accesses in the destination scope that must wait for `src_access` to be made + /// available and visible. + /// + /// The default value is [`AccessFlags::empty()`]. + pub dst_access: AccessFlags, + + /// The buffer to apply the barrier to. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub buffer: Id, + + /// The byte range of `buffer` to apply the barrier to. + /// + /// The default value is empty, which must be overridden. + pub range: Range, + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for BufferMemoryBarrier<'_> { + #[inline] + fn default() -> Self { + BufferMemoryBarrier { + src_stages: PipelineStages::empty(), + src_access: AccessFlags::empty(), + dst_stages: PipelineStages::empty(), + dst_access: AccessFlags::empty(), + buffer: Id::INVALID, + range: 0..0, + _ne: crate::NE, + } + } +} + +/// A memory barrier that is applied to a single image. +#[derive(Clone, Debug)] +pub struct ImageMemoryBarrier<'a> { + /// The pipeline stages in the source scope to wait for. + /// + /// The default value is [`PipelineStages::empty()`]. + pub src_stages: PipelineStages, + + /// The memory accesses in the source scope to make available and visible. + /// + /// The default value is [`AccessFlags::empty()`]. + pub src_access: AccessFlags, + + /// The pipeline stages in the destination scope that must wait for `src_stages`. + /// + /// The default value is [`PipelineStages::empty()`]. + pub dst_stages: PipelineStages, + + /// The memory accesses in the destination scope that must wait for `src_access` to be made + /// available and visible. + /// + /// The default value is [`AccessFlags::empty()`]. + pub dst_access: AccessFlags, + + /// The layout that the specified `subresource_range` of `image` is expected to be in when the + /// source scope completes. + /// + /// The default value is [`ImageLayout::Undefined`]. + pub old_layout: ImageLayout, + + /// The layout that the specified `subresource_range` of `image` will be transitioned to before + /// the destination scope begins. + /// + /// The default value is [`ImageLayout::Undefined`]. + pub new_layout: ImageLayout, + + /// The image to apply the barrier to. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub image: Id, + + /// The subresource range of `image` to apply the barrier to. + /// + /// The default value is empty, which must be overridden. + pub subresource_range: ImageSubresourceRange, + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for ImageMemoryBarrier<'_> { + #[inline] + fn default() -> Self { + ImageMemoryBarrier { + src_stages: PipelineStages::empty(), + src_access: AccessFlags::empty(), + dst_stages: PipelineStages::empty(), + dst_access: AccessFlags::empty(), + old_layout: ImageLayout::Undefined, + new_layout: ImageLayout::Undefined, + image: Id::INVALID, + subresource_range: ImageSubresourceRange { + aspects: ImageAspects::empty(), + mip_levels: 0..0, + array_layers: 0..0, + }, + _ne: crate::NE, + } + } +} diff --git a/vulkano-taskgraph/src/command_buffer/mod.rs b/vulkano-taskgraph/src/command_buffer/mod.rs new file mode 100644 index 0000000000..ffeb09715c --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/mod.rs @@ -0,0 +1,110 @@ +//! Recording commands to execute on the device. + +#[allow(unused_imports)] // everything is exported for future-proofing +pub use self::commands::{clear::*, copy::*, dynamic_state::*, pipeline::*, sync::*}; +use crate::{graph::ResourceMap, resource::DeathRow, Id}; +use ash::vk; +use std::sync::Arc; +use vulkano::{ + buffer::Buffer, + command_buffer::sys::RawRecordingCommandBuffer, + device::{Device, DeviceOwned}, + image::Image, + VulkanObject, +}; + +mod commands; + +/// A command buffer in the recording state. +/// +/// Unlike [`RawRecordingCommandBuffer`], this type has knowledge of the current task context and +/// can therefore validate resource accesses. (TODO) +pub struct RecordingCommandBuffer<'a> { + inner: &'a mut RawRecordingCommandBuffer, + accesses: ResourceAccesses<'a>, + death_row: &'a mut DeathRow, +} + +struct ResourceAccesses<'a> { + resource_map: &'a ResourceMap<'a>, +} + +impl<'a> RecordingCommandBuffer<'a> { + pub(crate) unsafe fn new( + inner: &'a mut RawRecordingCommandBuffer, + resource_map: &'a ResourceMap<'a>, + death_row: &'a mut DeathRow, + ) -> Self { + RecordingCommandBuffer { + inner, + accesses: ResourceAccesses { resource_map }, + death_row, + } + } + + /// Returns the underlying raw command buffer. + /// + /// While this method is safe, using the command buffer isn't. You must guarantee that any + /// subresources you use while recording commands are either accounted for in the [task's + /// access set], or that those subresources don't require any synchronization (including layout + /// transitions and queue family ownership transfers), or that no other task is accessing the + /// subresources at the same time without appropriate synchronization. + #[inline] + pub fn as_raw(&mut self) -> &mut RawRecordingCommandBuffer { + self.inner + } +} + +unsafe impl DeviceOwned for RecordingCommandBuffer<'_> { + #[inline] + fn device(&self) -> &Arc { + self.inner.device() + } +} + +unsafe impl VulkanObject for RecordingCommandBuffer<'_> { + type Handle = vk::CommandBuffer; + + #[inline] + fn handle(&self) -> Self::Handle { + self.inner.handle() + } +} + +impl<'a> ResourceAccesses<'a> { + unsafe fn buffer_unchecked(&self, id: Id) -> &'a Arc { + if id.is_virtual() { + // SAFETY: + // * The caller of `Task::execute` must ensure that `self.resource_map` maps the virtual + // IDs of the graph exhaustively. + // * The caller must ensure that `id` is valid. + unsafe { self.resource_map.buffer_unchecked(id) }.buffer() + } else { + let resources = self.resource_map.resources(); + + // SAFETY: + // * `ResourceMap` owns an `epoch::Guard`. + // * The caller must ensure that `id` is valid. + unsafe { resources.buffer_unchecked_unprotected(id) }.buffer() + } + } + + unsafe fn image_unchecked(&self, id: Id) -> &'a Arc { + if id.is_virtual() { + // SAFETY: + // * The caller must ensure that `id` is valid. + // * The caller of `Task::execute` must ensure that `self.resource_map` maps the virtual + // IDs of the graph exhaustively. + unsafe { self.resource_map.image_unchecked(id) }.image() + } else { + let resources = self.resource_map.resources(); + + // SAFETY: + // * The caller must ensure that `id` is valid. + // * `ResourceMap` owns an `epoch::Guard`. + unsafe { resources.image_unchecked_unprotected(id) }.image() + } + } +} + +type Result> = ::std::result::Result; diff --git a/vulkano-taskgraph/src/graph/compile.rs b/vulkano-taskgraph/src/graph/compile.rs index 2025e1318d..c943409725 100644 --- a/vulkano-taskgraph/src/graph/compile.rs +++ b/vulkano-taskgraph/src/graph/compile.rs @@ -47,9 +47,9 @@ impl TaskGraph { /// [directed cycles]: https://en.wikipedia.org/wiki/Cycle_(graph_theory)#Directed_circuit_and_directed_cycle pub unsafe fn compile( mut self, - compile_info: CompileInfo, + compile_info: &CompileInfo<'_>, ) -> Result, CompileError> { - let CompileInfo { + let &CompileInfo { queues, present_queue, flight_id, @@ -60,7 +60,7 @@ impl TaskGraph { let device = &self.device().clone(); - for queue in &queues { + for queue in queues { assert_eq!(queue.device(), device); assert_eq!( queues @@ -86,14 +86,14 @@ impl TaskGraph { }; unsafe { self.dependency_levels(&topological_order) }; let queue_family_indices = - match unsafe { self.queue_family_indices(device, &queues, &topological_order) } { + match unsafe { self.queue_family_indices(device, queues, &topological_order) } { Ok(queue_family_indices) => queue_family_indices, Err(kind) => return Err(CompileError::new(self, kind)), }; let mut queues_by_queue_family_index: SmallVec<[_; 8]> = smallvec![None; *queue_family_indices.iter().max().unwrap() as usize + 1]; - for queue in &queues { + for &queue in queues { if let Some(x) = queues_by_queue_family_index.get_mut(queue.queue_family_index() as usize) { @@ -262,7 +262,7 @@ impl TaskGraph { if should_submit { let queue = queues_by_queue_family_index[task_node.queue_family_index as usize] .unwrap(); - state.submit(queue.clone()); + state.submit(queue); prev_submission_end = i + 1; break; } @@ -278,7 +278,7 @@ impl TaskGraph { } state.flush_submit(); - state.submit(state.present_queue.clone().unwrap()); + state.submit(state.present_queue.unwrap()); } let semaphores = match (0..semaphore_count) @@ -304,7 +304,7 @@ impl TaskGraph { image_barriers: state.image_barriers, semaphores: RefCell::new(semaphores), swapchains, - present_queue: state.present_queue, + present_queue: state.present_queue.cloned(), last_accesses: prev_accesses, }) } @@ -447,7 +447,7 @@ impl TaskGraph { unsafe fn queue_family_indices( &mut self, device: &Device, - queues: &[Arc], + queues: &[&Arc], topological_order: &[NodeIndex], ) -> Result, CompileErrorKind> { let queue_family_properties = device.physical_device().queue_family_properties(); @@ -625,7 +625,7 @@ struct CompileState<'a> { submissions: Vec, buffer_barriers: Vec, image_barriers: Vec, - present_queue: Option>, + present_queue: Option<&'a Arc>, initial_buffer_barrier_range: Range, initial_image_barrier_range: Range, has_flushed_submit: bool, @@ -636,7 +636,7 @@ struct CompileState<'a> { } impl<'a> CompileState<'a> { - fn new(prev_accesses: &'a mut [ResourceAccess], present_queue: Option>) -> Self { + fn new(prev_accesses: &'a mut [ResourceAccess], present_queue: Option<&'a Arc>) -> Self { CompileState { prev_accesses, instructions: Vec::new(), @@ -932,7 +932,7 @@ impl<'a> CompileState<'a> { self.should_flush_submit = false; } - fn submit(&mut self, queue: Arc) { + fn submit(&mut self, queue: &Arc) { self.instructions.push(Instruction::Submit); let prev_instruction_range_end = self @@ -941,7 +941,7 @@ impl<'a> CompileState<'a> { .map(|s| s.instruction_range.end) .unwrap_or(0); self.submissions.push(Submission { - queue, + queue: queue.clone(), initial_buffer_barrier_range: self.initial_buffer_barrier_range.clone(), initial_image_barrier_range: self.initial_image_barrier_range.clone(), instruction_range: prev_instruction_range_end..self.instructions.len(), @@ -961,13 +961,13 @@ impl ExecutableTaskGraph { /// /// [compile]: TaskGraph::compile #[derive(Clone, Debug)] -pub struct CompileInfo { +pub struct CompileInfo<'a> { /// The queues to work with. /// /// You must supply at least one queue and all queues must be from unique queue families. /// /// The default value is empty, which must be overridden. - pub queues: Vec>, + pub queues: &'a [&'a Arc], /// The queue to use for swapchain presentation, if any. /// @@ -977,21 +977,21 @@ pub struct CompileInfo { /// The default value is `None`. /// /// [`queues`]: Self::queues - pub present_queue: Option>, + pub present_queue: Option<&'a Arc>, /// The flight which will be executed. /// /// The default value is `Id::INVALID`, which must be overridden. pub flight_id: Id, - pub _ne: vulkano::NonExhaustive, + pub _ne: crate::NonExhaustive<'a>, } -impl Default for CompileInfo { +impl Default for CompileInfo<'_> { #[inline] fn default() -> Self { CompileInfo { - queues: Vec::new(), + queues: &[], present_queue: None, flight_id: Id::INVALID, _ne: crate::NE, @@ -1112,7 +1112,7 @@ mod tests { fn unconnected() { let (resources, queues) = test_queues!(); let compile_info = CompileInfo { - queues, + queues: &queues.iter().collect::>(), ..Default::default() }; @@ -1124,7 +1124,7 @@ mod tests { // ┌───┐ // │ B │ // └───┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1133,7 +1133,7 @@ mod tests { .build(); assert!(matches!( - unsafe { graph.compile(compile_info.clone()) }, + unsafe { graph.compile(&compile_info) }, Err(CompileError { kind: CompileErrorKind::Unconnected, .. @@ -1155,7 +1155,7 @@ mod tests { // │ ┌───┐ // └─►│ D │ // └───┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1172,7 +1172,7 @@ mod tests { graph.add_edge(b, d).unwrap(); assert!(matches!( - unsafe { graph.compile(compile_info.clone()) }, + unsafe { graph.compile(&compile_info) }, Err(CompileError { kind: CompileErrorKind::Unconnected, .. @@ -1190,7 +1190,7 @@ mod tests { // └───┘│ └───┘│ └───┘┌─►│ G │ // │ └──────┘┌►│ │ // └──────────────┘ └───┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1221,7 +1221,7 @@ mod tests { graph.add_edge(f, g).unwrap(); assert!(matches!( - unsafe { graph.compile(compile_info) }, + unsafe { graph.compile(&compile_info) }, Err(CompileError { kind: CompileErrorKind::Unconnected, .. @@ -1234,7 +1234,7 @@ mod tests { fn cycle() { let (resources, queues) = test_queues!(); let compile_info = CompileInfo { - queues, + queues: &queues.iter().collect::>(), ..Default::default() }; @@ -1243,7 +1243,7 @@ mod tests { // ┌►│ A ├─►│ B ├─►│ C ├┐ // │ └───┘ └───┘ └───┘│ // └────────────────────┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1258,7 +1258,7 @@ mod tests { graph.add_edge(c, a).unwrap(); assert!(matches!( - unsafe { graph.compile(compile_info.clone()) }, + unsafe { graph.compile(&compile_info) }, Err(CompileError { kind: CompileErrorKind::Cycle, .. @@ -1275,7 +1275,7 @@ mod tests { // │ └►│ D ├─►│ E ├┴►│ F ├┐ // │ └───┘ └───┘ └───┘│ // └───────────────────────────┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1303,7 +1303,7 @@ mod tests { graph.add_edge(f, a).unwrap(); assert!(matches!( - unsafe { graph.compile(compile_info.clone()) }, + unsafe { graph.compile(&compile_info) }, Err(CompileError { kind: CompileErrorKind::Cycle, .. @@ -1322,7 +1322,7 @@ mod tests { // │ ┌►└───┘ └───┘ └───┘││ // │ └────────────────────┘│ // └───────────────────────────┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1351,7 +1351,7 @@ mod tests { graph.add_edge(f, b).unwrap(); assert!(matches!( - unsafe { graph.compile(compile_info) }, + unsafe { graph.compile(&compile_info) }, Err(CompileError { kind: CompileErrorKind::Cycle, .. @@ -1364,12 +1364,12 @@ mod tests { fn initial_pipeline_barrier() { let (resources, queues) = test_queues!(); let compile_info = CompileInfo { - queues, + queues: &queues.iter().collect::>(), ..Default::default() }; { - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let buffer = graph.add_buffer(&BufferCreateInfo::default()); let image = graph.add_image(&ImageCreateInfo::default()); let node = graph @@ -1382,7 +1382,7 @@ mod tests { ) .build(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); assert_matches_instructions!( graph, @@ -1429,7 +1429,7 @@ mod tests { } let compile_info = CompileInfo { - queues, + queues: &queues.iter().collect::>(), ..Default::default() }; @@ -1444,7 +1444,7 @@ mod tests { // │└►┌───┐ // └─►│ C │ // └───┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1457,7 +1457,7 @@ mod tests { graph.add_edge(a, c).unwrap(); graph.add_edge(b, c).unwrap(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); assert_matches_instructions!( graph, @@ -1500,7 +1500,7 @@ mod tests { // │ ┌───┐ // └►│ C │ // └───┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1513,7 +1513,7 @@ mod tests { graph.add_edge(a, b).unwrap(); graph.add_edge(a, c).unwrap(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); assert_matches_instructions!( graph, @@ -1556,7 +1556,7 @@ mod tests { // │ ┌───┐└►┌───┐│ // └►│ C ├─►│ D ├┘ // └───┘ └───┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1578,7 +1578,7 @@ mod tests { graph.add_edge(c, d).unwrap(); graph.add_edge(d, e).unwrap(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); // TODO: This could be brought down to 3 submissions with task reordering. assert_matches_instructions!( @@ -1645,12 +1645,12 @@ mod tests { } let compile_info = CompileInfo { - queues, + queues: &queues.iter().collect::>(), ..Default::default() }; { - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let buffer1 = graph.add_buffer(&BufferCreateInfo::default()); let buffer2 = graph.add_buffer(&BufferCreateInfo::default()); let image1 = graph.add_image(&ImageCreateInfo::default()); @@ -1687,7 +1687,7 @@ mod tests { .build(); graph.add_edge(compute_node, graphics_node).unwrap(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); assert_matches_instructions!( graph, @@ -1789,7 +1789,7 @@ mod tests { } { - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let sharing = Sharing::Concurrent( compile_info .queues @@ -1845,7 +1845,7 @@ mod tests { .build(); graph.add_edge(compute_node, graphics_node).unwrap(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); assert_matches_instructions!( graph, @@ -1900,13 +1900,13 @@ mod tests { queue_flags.contains(QueueFlags::GRAPHICS) }); let compile_info = CompileInfo { - queues: queues.clone(), - present_queue: Some(present_queue.unwrap().clone()), + queues: &queues.iter().collect::>(), + present_queue: Some(present_queue.unwrap()), ..Default::default() }; { - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let swapchain1 = graph.add_swapchain(&SwapchainCreateInfo::default()); let swapchain2 = graph.add_swapchain(&SwapchainCreateInfo::default()); let node = graph @@ -1923,7 +1923,7 @@ mod tests { ) .build(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); assert_matches_instructions!( graph, @@ -2006,13 +2006,13 @@ mod tests { } let compile_info = CompileInfo { - queues: queues.clone(), - present_queue: Some(present_queue.unwrap().clone()), + queues: &queues.iter().collect::>(), + present_queue: Some(present_queue.unwrap()), ..Default::default() }; { - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let concurrent_sharing = Sharing::Concurrent( compile_info .queues @@ -2054,7 +2054,7 @@ mod tests { ) .build(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); assert_matches_instructions!( graph, diff --git a/vulkano-taskgraph/src/graph/execute.rs b/vulkano-taskgraph/src/graph/execute.rs index 602fd27afe..d32c45a070 100644 --- a/vulkano-taskgraph/src/graph/execute.rs +++ b/vulkano-taskgraph/src/graph/execute.rs @@ -2,6 +2,7 @@ use super::{ BarrierIndex, ExecutableTaskGraph, Instruction, NodeIndex, ResourceAccess, SemaphoreIndex, }; use crate::{ + command_buffer::RecordingCommandBuffer, resource::{ BufferAccess, BufferState, DeathRow, ImageAccess, ImageState, Resources, SwapchainState, }, @@ -11,7 +12,6 @@ use ash::vk; use concurrent_slotmap::epoch; use smallvec::SmallVec; use std::{ - cell::Cell, error::Error, fmt, mem, ops::Range, @@ -48,7 +48,7 @@ impl ExecutableTaskGraph { /// - Panics if `resource_map` maps to any swapchain that isn't owned by the flight. /// - Panics if the oldest frame of the flight wasn't [waited] on. /// - /// [waited]: Flight::wait + /// [waited]: crate::resource::Flight::wait pub unsafe fn execute( &self, resource_map: ResourceMap<'_>, @@ -280,7 +280,7 @@ impl ExecutableTaskGraph { for instruction in self.instructions.iter().cloned() { if execute_initial_barriers { - let submission = state.current_submission(); + let submission = current_submission!(state); state.initial_pipeline_barrier( submission.initial_buffer_barrier_range.clone(), submission.initial_image_barrier_range.clone(), @@ -308,7 +308,7 @@ impl ExecutableTaskGraph { buffer_barrier_range, image_barrier_range, } => { - state.pipeline_barrier(buffer_barrier_range, image_barrier_range); + state.pipeline_barrier(buffer_barrier_range, image_barrier_range)?; } Instruction::SignalSemaphore { semaphore_index, @@ -369,7 +369,7 @@ impl ExecutableTaskGraph { for instruction in self.instructions.iter().cloned() { if execute_initial_barriers { - let submission = state.current_submission(); + let submission = current_submission!(state); state.initial_pipeline_barrier( submission.initial_buffer_barrier_range.clone(), submission.initial_image_barrier_range.clone(), @@ -397,7 +397,7 @@ impl ExecutableTaskGraph { buffer_barrier_range, image_barrier_range, } => { - state.pipeline_barrier(buffer_barrier_range, image_barrier_range); + state.pipeline_barrier(buffer_barrier_range, image_barrier_range)?; } Instruction::SignalSemaphore { semaphore_index, @@ -653,9 +653,6 @@ impl<'a, W: ?Sized + 'static> ExecuteState2<'a, W> { queue_submit2 = fns.khr_synchronization2.queue_submit2_khr; } - let current_command_buffer = - create_command_buffer(resource_map, &executable.submissions[0].queue)?; - Ok(ExecuteState2 { executable, resource_map, @@ -668,17 +665,13 @@ impl<'a, W: ?Sized + 'static> ExecuteState2<'a, W> { queue_submit2, per_submits: SmallVec::new(), current_per_submit: PerSubmitInfo2::default(), - current_command_buffer: Some(current_command_buffer), + current_command_buffer: None, command_buffers: Vec::new(), current_buffer_barriers: Vec::new(), current_image_barriers: Vec::new(), }) } - fn current_submission(&self) -> &super::Submission { - &self.executable.submissions[*self.submission_count] - } - fn initial_pipeline_barrier( &mut self, buffer_barrier_range: Range, @@ -690,7 +683,7 @@ impl<'a, W: ?Sized + 'static> ExecuteState2<'a, W> { fn convert_initial_buffer_barriers(&mut self, barrier_range: Range) { let barrier_range = barrier_range.start as usize..barrier_range.end as usize; - let queue_family_index = self.current_submission().queue.queue_family_index(); + let queue_family_index = current_submission!(self).queue.queue_family_index(); for barrier in &self.executable.buffer_barriers[barrier_range] { let state = unsafe { self.resource_map.buffer_unchecked(barrier.buffer) }; @@ -732,7 +725,7 @@ impl<'a, W: ?Sized + 'static> ExecuteState2<'a, W> { fn convert_initial_image_barriers(&mut self, barrier_range: Range) { let barrier_range = barrier_range.start as usize..barrier_range.end as usize; - let queue_family_index = self.current_submission().queue.queue_family_index(); + let queue_family_index = current_submission!(self).queue.queue_family_index(); for barrier in &self.executable.image_barriers[barrier_range] { let (image, access) = match barrier.image.object_type() { @@ -810,20 +803,25 @@ impl<'a, W: ?Sized + 'static> ExecuteState2<'a, W> { fn execute_task(&mut self, node_index: NodeIndex) -> Result { if !self.current_buffer_barriers.is_empty() || !self.current_image_barriers.is_empty() { - self.flush_barriers(); + self.flush_barriers()?; } let task_node = unsafe { self.executable.graph.nodes.task_node_unchecked(node_index) }; let task = &task_node.task; - let current_command_buffer = self.current_command_buffer.as_mut().unwrap(); + let mut current_command_buffer = unsafe { + RecordingCommandBuffer::new( + current_command_buffer!(self), + self.resource_map, + self.death_row, + ) + }; let mut context = TaskContext { resource_map: self.resource_map, - death_row: Cell::new(Some(self.death_row)), current_frame_index: self.current_frame_index, - command_buffers: Cell::new(Some(&mut self.command_buffers)), + command_buffers: &mut self.command_buffers, }; - unsafe { task.execute(current_command_buffer, &mut context, self.world) } + unsafe { task.execute(&mut current_command_buffer, &mut context, self.world) } .map_err(|error| ExecuteError::Task { node_index, error })?; if !self.command_buffers.is_empty() { @@ -844,11 +842,11 @@ impl<'a, W: ?Sized + 'static> ExecuteState2<'a, W> { &mut self, buffer_barrier_range: Range, image_barrier_range: Range, - ) { + ) -> Result { self.convert_buffer_barriers(buffer_barrier_range); self.convert_image_barriers(image_barrier_range); - self.flush_barriers(); + self.flush_barriers() } fn convert_buffer_barriers(&mut self, barrier_range: Range) { @@ -951,10 +949,10 @@ impl<'a, W: ?Sized + 'static> ExecuteState2<'a, W> { ); } - fn flush_barriers(&mut self) { + fn flush_barriers(&mut self) -> Result { unsafe { (self.cmd_pipeline_barrier2)( - self.current_command_buffer.as_ref().unwrap().handle(), + current_command_buffer!(self).handle(), &vk::DependencyInfo::default() .buffer_memory_barriers(&self.current_buffer_barriers) .image_memory_barriers(&self.current_image_barriers), @@ -963,6 +961,8 @@ impl<'a, W: ?Sized + 'static> ExecuteState2<'a, W> { self.current_buffer_barriers.clear(); self.current_image_barriers.clear(); + + Ok(()) } fn flush_submit(&mut self) -> Result { @@ -980,7 +980,7 @@ impl<'a, W: ?Sized + 'static> ExecuteState2<'a, W> { .flush_mapped_memory_ranges(self.resource_map) }?; - let submission = self.current_submission(); + let submission = current_submission!(self); let mut submit_infos = SmallVec::<[_; 4]>::with_capacity(self.per_submits.len()); submit_infos.extend(self.per_submits.iter().map(|per_submit| { @@ -1010,23 +1010,21 @@ impl<'a, W: ?Sized + 'static> ExecuteState2<'a, W> { .map_err(VulkanError::from) })?; + drop(submit_infos); + self.per_submits.clear(); + *self.submission_count += 1; Ok(()) } unsafe fn flush_current_command_buffer(&mut self) -> Result { - if let Some(command_buffer) = self.current_command_buffer.take() { - let command_buffer = unsafe { command_buffer.end() }?; - self.current_per_submit.command_buffer_infos.push( - vk::CommandBufferSubmitInfo::default().command_buffer(command_buffer.handle()), - ); - self.death_row.push(Arc::new(command_buffer)); - self.current_command_buffer = Some(create_command_buffer( - self.resource_map, - &self.current_submission().queue, - )?); - } + let current_command_buffer = self.current_command_buffer.take().unwrap(); + let command_buffer = unsafe { current_command_buffer.end() }?; + self.current_per_submit + .command_buffer_infos + .push(vk::CommandBufferSubmitInfo::default().command_buffer(command_buffer.handle())); + self.death_row.push(Arc::new(command_buffer)); Ok(()) } @@ -1074,9 +1072,6 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> { let cmd_pipeline_barrier = fns.v1_0.cmd_pipeline_barrier; let queue_submit = fns.v1_0.queue_submit; - let current_command_buffer = - create_command_buffer(resource_map, &executable.submissions[0].queue)?; - Ok(ExecuteState { executable, resource_map, @@ -1089,7 +1084,7 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> { queue_submit, per_submits: SmallVec::new(), current_per_submit: PerSubmitInfo::default(), - current_command_buffer: Some(current_command_buffer), + current_command_buffer: None, command_buffers: Vec::new(), current_buffer_barriers: Vec::new(), current_image_barriers: Vec::new(), @@ -1098,10 +1093,6 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> { }) } - fn current_submission(&self) -> &super::Submission { - &self.executable.submissions[*self.submission_count] - } - fn initial_pipeline_barrier( &mut self, buffer_barrier_range: Range, @@ -1113,7 +1104,7 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> { fn convert_initial_buffer_barriers(&mut self, barrier_range: Range) { let barrier_range = barrier_range.start as usize..barrier_range.end as usize; - let queue_family_index = self.current_submission().queue.queue_family_index(); + let queue_family_index = current_submission!(self).queue.queue_family_index(); for barrier in &self.executable.buffer_barriers[barrier_range] { let state = unsafe { self.resource_map.buffer_unchecked(barrier.buffer) }; @@ -1156,7 +1147,7 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> { fn convert_initial_image_barriers(&mut self, barrier_range: Range) { let barrier_range = barrier_range.start as usize..barrier_range.end as usize; - let queue_family_index = self.current_submission().queue.queue_family_index(); + let queue_family_index = current_submission!(self).queue.queue_family_index(); for barrier in &self.executable.image_barriers[barrier_range] { let (image, access) = match barrier.image.object_type() { @@ -1237,20 +1228,25 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> { fn execute_task(&mut self, node_index: NodeIndex) -> Result { if !self.current_buffer_barriers.is_empty() || !self.current_image_barriers.is_empty() { - self.flush_barriers(); + self.flush_barriers()?; } let task_node = unsafe { self.executable.graph.nodes.task_node_unchecked(node_index) }; let task = &task_node.task; - let current_command_buffer = self.current_command_buffer.as_mut().unwrap(); + let mut current_command_buffer = unsafe { + RecordingCommandBuffer::new( + current_command_buffer!(self), + self.resource_map, + self.death_row, + ) + }; let mut context = TaskContext { resource_map: self.resource_map, - death_row: Cell::new(Some(self.death_row)), current_frame_index: self.current_frame_index, - command_buffers: Cell::new(Some(&mut self.command_buffers)), + command_buffers: &mut self.command_buffers, }; - unsafe { task.execute(current_command_buffer, &mut context, self.world) } + unsafe { task.execute(&mut current_command_buffer, &mut context, self.world) } .map_err(|error| ExecuteError::Task { node_index, error })?; if !self.command_buffers.is_empty() { @@ -1271,11 +1267,11 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> { &mut self, buffer_barrier_range: Range, image_barrier_range: Range, - ) { + ) -> Result { self.convert_buffer_barriers(buffer_barrier_range); self.convert_image_barriers(image_barrier_range); - self.flush_barriers(); + self.flush_barriers() } fn convert_buffer_barriers(&mut self, barrier_range: Range) { @@ -1384,7 +1380,7 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> { Ok(()) } - fn flush_barriers(&mut self) { + fn flush_barriers(&mut self) -> Result { if self.current_src_stage_mask.is_empty() { self.current_src_stage_mask = vk::PipelineStageFlags::TOP_OF_PIPE; } @@ -1395,7 +1391,7 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> { unsafe { (self.cmd_pipeline_barrier)( - self.current_command_buffer.as_ref().unwrap().handle(), + current_command_buffer!(self).handle(), self.current_src_stage_mask, self.current_dst_stage_mask, vk::DependencyFlags::empty(), @@ -1412,6 +1408,8 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> { self.current_image_barriers.clear(); self.current_src_stage_mask = vk::PipelineStageFlags::empty(); self.current_dst_stage_mask = vk::PipelineStageFlags::empty(); + + Ok(()) } fn submit(&mut self) -> Result { @@ -1420,7 +1418,7 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> { .flush_mapped_memory_ranges(self.resource_map) }?; - let submission = self.current_submission(); + let submission = current_submission!(self); let mut submit_infos = SmallVec::<[_; 4]>::with_capacity(self.per_submits.len()); submit_infos.extend(self.per_submits.iter().map(|per_submit| { @@ -1451,39 +1449,57 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> { .map_err(VulkanError::from) })?; + drop(submit_infos); + self.per_submits.clear(); + *self.submission_count += 1; Ok(()) } unsafe fn flush_current_command_buffer(&mut self) -> Result { - if let Some(command_buffer) = self.current_command_buffer.take() { - let command_buffer = unsafe { command_buffer.end() }?; - self.current_per_submit - .command_buffers - .push(command_buffer.handle()); - self.death_row.push(Arc::new(command_buffer)); - self.current_command_buffer = Some(create_command_buffer( - self.resource_map, - &self.current_submission().queue, - )?); - } + let current_command_buffer = self.current_command_buffer.take().unwrap(); + let command_buffer = unsafe { current_command_buffer.end() }?; + self.current_per_submit + .command_buffers + .push(command_buffer.handle()); + self.death_row.push(Arc::new(command_buffer)); Ok(()) } } +macro_rules! current_submission { + ($state:expr) => { + &$state.executable.submissions[*$state.submission_count] + }; +} +use current_submission; + +macro_rules! current_command_buffer { + ($state:expr) => {{ + if $state.current_command_buffer.is_none() { + $state.current_command_buffer = Some(create_command_buffer( + $state.resource_map, + ¤t_submission!($state).queue, + )?); + } + + $state.current_command_buffer.as_mut().unwrap() + }}; +} +use current_command_buffer; + fn create_command_buffer( resource_map: &ResourceMap<'_>, queue: &Queue, ) -> Result { + let allocator = resource_map.physical_resources.command_buffer_allocator(); + // SAFETY: The parameters are valid. unsafe { RawRecordingCommandBuffer::new_unchecked( - resource_map - .physical_resources - .command_buffer_allocator() - .clone(), + allocator.clone(), queue.queue_family_index(), CommandBufferLevel::Primary, CommandBufferBeginInfo { @@ -1965,6 +1981,11 @@ impl<'a> ResourceMap<'a> { } pub(crate) unsafe fn buffer_unchecked(&self, id: Id) -> &BufferState { + #[cfg(debug_assertions)] + if self.virtual_resources.get(id.erase()).is_err() { + std::process::abort(); + } + // SAFETY: The caller must ensure that `id` is a valid virtual ID. let &slot = unsafe { self.map.get_unchecked(id.index() as usize) }; @@ -1980,6 +2001,11 @@ impl<'a> ResourceMap<'a> { } pub(crate) unsafe fn image_unchecked(&self, id: Id) -> &ImageState { + #[cfg(debug_assertions)] + if self.virtual_resources.get(id.erase()).is_err() { + std::process::abort(); + } + // SAFETY: The caller must ensure that `id` is a valid virtual ID. let &slot = unsafe { self.map.get_unchecked(id.index() as usize) }; @@ -1998,6 +2024,11 @@ impl<'a> ResourceMap<'a> { } pub(crate) unsafe fn swapchain_unchecked(&self, id: Id) -> &SwapchainState { + #[cfg(debug_assertions)] + if self.virtual_resources.get(id.erase()).is_err() { + std::process::abort(); + } + // SAFETY: The caller must ensure that `id` is a valid virtual ID. let &slot = unsafe { self.map.get_unchecked(id.index() as usize) }; diff --git a/vulkano-taskgraph/src/graph/mod.rs b/vulkano-taskgraph/src/graph/mod.rs index d5c707381f..7d1e457c48 100644 --- a/vulkano-taskgraph/src/graph/mod.rs +++ b/vulkano-taskgraph/src/graph/mod.rs @@ -72,7 +72,7 @@ impl TaskGraph { /// maximum number of virtual resources the graph can ever have. #[must_use] pub fn new( - physical_resources: Arc, + physical_resources: &Arc, max_nodes: u32, max_resources: u32, ) -> Self { @@ -82,7 +82,7 @@ impl TaskGraph { }, resources: Resources { inner: SlotMap::new(max_resources), - physical_resources, + physical_resources: physical_resources.clone(), physical_map: HashMap::default(), host_reads: Vec::new(), host_writes: Vec::new(), @@ -565,7 +565,7 @@ struct ResourceAccess { impl TaskNode { fn new(queue_family_type: QueueFamilyType, task: impl Task) -> Self { TaskNode { - accesses: ResourceAccesses { inner: Vec::new() }, + accesses: ResourceAccesses::new(), queue_family_type, queue_family_index: 0, dependency_level_index: 0, @@ -596,6 +596,10 @@ impl TaskNode { } impl ResourceAccesses { + pub(crate) const fn new() -> Self { + ResourceAccesses { inner: Vec::new() } + } + fn get_mut( &mut self, resources: &mut Resources, @@ -1022,7 +1026,7 @@ mod tests { #[test] fn basic_usage1() { let (resources, _) = test_queues!(); - let mut graph = TaskGraph::<()>::new(resources, 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let x = graph .create_task_node("X", QueueFamilyType::Graphics, PhantomData) @@ -1057,7 +1061,7 @@ mod tests { #[test] fn basic_usage2() { let (resources, _) = test_queues!(); - let mut graph = TaskGraph::<()>::new(resources, 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let x = graph .create_task_node("X", QueueFamilyType::Graphics, PhantomData) @@ -1094,7 +1098,7 @@ mod tests { #[test] fn self_referential_node() { let (resources, _) = test_queues!(); - let mut graph = TaskGraph::<()>::new(resources, 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let x = graph .create_task_node("X", QueueFamilyType::Graphics, PhantomData) diff --git a/vulkano-taskgraph/src/lib.rs b/vulkano-taskgraph/src/lib.rs index 7edf77375c..caa2498db9 100644 --- a/vulkano-taskgraph/src/lib.rs +++ b/vulkano-taskgraph/src/lib.rs @@ -1,10 +1,11 @@ #![forbid(unsafe_op_in_unsafe_fn)] +use command_buffer::RecordingCommandBuffer; use concurrent_slotmap::SlotId; use graph::{CompileInfo, ExecuteError, ResourceMap, TaskGraph}; use resource::{ - AccessType, BufferState, DeathRow, Flight, HostAccessType, ImageLayoutType, ImageState, - Resources, SwapchainState, + AccessType, BufferState, Flight, HostAccessType, ImageLayoutType, ImageState, Resources, + SwapchainState, }; use std::{ any::{Any, TypeId}, @@ -20,29 +21,30 @@ use std::{ }; use vulkano::{ buffer::{Buffer, BufferContents, BufferMemory, Subbuffer}, - command_buffer::sys::{RawCommandBuffer, RawRecordingCommandBuffer}, + command_buffer::sys::RawCommandBuffer, device::Queue, image::Image, swapchain::Swapchain, DeviceSize, ValidationError, }; +pub mod command_buffer; pub mod graph; pub mod resource; /// Creates a [`TaskGraph`] with one task node, compiles it, and executes it. pub unsafe fn execute( - queue: Arc, - resources: Arc, + queue: &Arc, + resources: &Arc, flight_id: Id, - task: impl FnOnce(&mut RawRecordingCommandBuffer, &mut TaskContext<'_>) -> TaskResult, + task: impl FnOnce(&mut RecordingCommandBuffer<'_>, &mut TaskContext<'_>) -> TaskResult, host_buffer_accesses: impl IntoIterator, HostAccessType)>, buffer_accesses: impl IntoIterator, AccessType)>, image_accesses: impl IntoIterator, AccessType, ImageLayoutType)>, ) -> Result<(), ExecuteError> { #[repr(transparent)] struct OnceTask<'a>( - &'a dyn Fn(&mut RawRecordingCommandBuffer, &mut TaskContext<'_>) -> TaskResult, + &'a dyn Fn(&mut RecordingCommandBuffer<'_>, &mut TaskContext<'_>) -> TaskResult, ); // SAFETY: The task is constructed inside this function and never leaves its scope, so there is @@ -58,7 +60,7 @@ pub unsafe fn execute( unsafe fn execute( &self, - cbf: &mut RawRecordingCommandBuffer, + cbf: &mut RecordingCommandBuffer<'_>, tcx: &mut TaskContext<'_>, _: &Self::World, ) -> TaskResult { @@ -67,7 +69,7 @@ pub unsafe fn execute( } let task = Cell::new(Some(task)); - let trampoline = move |cbf: &mut RawRecordingCommandBuffer, tcx: &mut TaskContext<'_>| { + let trampoline = move |cbf: &mut RecordingCommandBuffer<'_>, tcx: &mut TaskContext<'_>| { // `ExecutableTaskGraph::execute` calls each task exactly once, and we only execute the // task graph once. (Cell::take(&task).unwrap())(cbf, tcx) @@ -101,8 +103,8 @@ pub unsafe fn execute( // * The user must ensure that there are no accesses that are incompatible with the queue. // * The user must ensure that there are no accesses incompatible with the device. let task_graph = unsafe { - task_graph.compile(CompileInfo { - queues: vec![queue], + task_graph.compile(&CompileInfo { + queues: &[queue], present_queue: None, flight_id, _ne: crate::NE, @@ -139,7 +141,7 @@ pub trait Task: Any + Send + Sync { /// [sharing mode]: vulkano::sync::Sharing unsafe fn execute( &self, - cbf: &mut RawRecordingCommandBuffer, + cbf: &mut RecordingCommandBuffer<'_>, tcx: &mut TaskContext<'_>, world: &Self::World, ) -> TaskResult; @@ -213,7 +215,7 @@ impl Task for PhantomData W> { unsafe fn execute( &self, - _cbf: &mut RawRecordingCommandBuffer, + _cbf: &mut RecordingCommandBuffer<'_>, _tcx: &mut TaskContext<'_>, _world: &Self::World, ) -> TaskResult { @@ -223,12 +225,11 @@ impl Task for PhantomData W> { /// The context of a task. /// -/// This gives you access to the current command buffer, resources, as well as resource cleanup. +/// This gives you access to the resources. pub struct TaskContext<'a> { resource_map: &'a ResourceMap<'a>, - death_row: Cell>, current_frame_index: u32, - command_buffers: Cell>>>, + command_buffers: &'a mut Vec>, } impl<'a> TaskContext<'a> { @@ -496,48 +497,6 @@ impl<'a> TaskContext<'a> { Ok(data) } - /// Queues the destruction of the buffer corresponding to `id` after the destruction of the - /// command buffer(s) for this task. - // FIXME: unsafe - #[inline] - pub unsafe fn destroy_buffer(&self, id: Id) -> TaskResult { - let state = unsafe { self.resource_map.resources().remove_buffer(id) }?; - let death_row = self.death_row.take().unwrap(); - // FIXME: - death_row.push(state.buffer().clone()); - self.death_row.set(Some(death_row)); - - Ok(()) - } - - /// Queues the destruction of the image corresponding to `id` after the destruction of the - /// command buffer(s) for this task. - // FIXME: unsafe - #[inline] - pub unsafe fn destroy_image(&self, id: Id) -> TaskResult { - let state = unsafe { self.resource_map.resources().remove_image(id) }?; - let death_row = self.death_row.take().unwrap(); - // FIXME: - death_row.push(state.image().clone()); - self.death_row.set(Some(death_row)); - - Ok(()) - } - - /// Queues the destruction of the swapchain corresponding to `id` after the destruction of the - /// command buffer(s) for this task. - // FIXME: unsafe - #[inline] - pub unsafe fn destroy_swapchain(&self, id: Id) -> TaskResult { - let state = unsafe { self.resource_map.resources().remove_swapchain(id) }?; - let death_row = self.death_row.take().unwrap(); - // FIXME: - death_row.push(state.swapchain().clone()); - self.death_row.set(Some(death_row)); - - Ok(()) - } - /// Pushes a command buffer into the list of command buffers to be executed on the queue. /// /// All command buffers will be executed in the order in which they are pushed after the task @@ -551,10 +510,8 @@ impl<'a> TaskContext<'a> { /// buffer must not do any accesses not accounted for in the [task's access set], or ensure /// that such accesses are appropriately synchronized. #[inline] - pub unsafe fn push_command_buffer(&self, command_buffer: Arc) { - let vec = self.command_buffers.take().unwrap(); - vec.push(command_buffer); - self.command_buffers.set(Some(vec)); + pub unsafe fn push_command_buffer(&mut self, command_buffer: Arc) { + self.command_buffers.push(command_buffer); } /// Extends the list of command buffers to be executed on the queue. @@ -569,12 +526,10 @@ impl<'a> TaskContext<'a> { /// [`push_command_buffer`]: Self::push_command_buffer #[inline] pub unsafe fn extend_command_buffers( - &self, + &mut self, command_buffers: impl IntoIterator>, ) { - let vec = self.command_buffers.take().unwrap(); - vec.extend(command_buffers); - self.command_buffers.set(Some(vec)); + self.command_buffers.extend(command_buffers); } } @@ -886,10 +841,10 @@ enum ObjectType { Flight = 3, } -// SAFETY: ZSTs can always be safely produced out of thin air, barring any safety invariants they -// might impose, which in the case of `NonExhaustive` are none. -const NE: vulkano::NonExhaustive = - unsafe { ::std::mem::transmute::<(), ::vulkano::NonExhaustive>(()) }; +#[derive(Clone, Copy, Debug)] +pub struct NonExhaustive<'a>(PhantomData<&'a ()>); + +const NE: NonExhaustive<'static> = NonExhaustive(PhantomData); #[cfg(test)] mod tests { @@ -932,7 +887,7 @@ mod tests { }; ( - $crate::resource::Resources::new(device, Default::default()), + $crate::resource::Resources::new(&device, &Default::default()), queues.collect::>(), ) }}; diff --git a/vulkano-taskgraph/src/resource.rs b/vulkano-taskgraph/src/resource.rs index 42a9a6fa79..3613453bda 100644 --- a/vulkano-taskgraph/src/resource.rs +++ b/vulkano-taskgraph/src/resource.rs @@ -121,9 +121,9 @@ impl Resources { /// /// - Panics if `device` already has a `Resources` collection associated with it. #[must_use] - pub fn new(device: Arc, create_info: ResourcesCreateInfo) -> Arc { + pub fn new(device: &Arc, create_info: &ResourcesCreateInfo<'_>) -> Arc { let mut registered_devices = REGISTERED_DEVICES.lock(); - let device_addr = Arc::as_ptr(&device) as usize; + let device_addr = Arc::as_ptr(device) as usize; assert!( !registered_devices.contains(&device_addr), @@ -141,7 +141,7 @@ impl Resources { let global = epoch::GlobalHandle::new(); Arc::new(Resources { - device, + device: device.clone(), memory_allocator, command_buffer_allocator, locals: ThreadLocal::new(), @@ -570,6 +570,11 @@ impl Resources { #[inline] pub(crate) unsafe fn buffer_unchecked_unprotected(&self, id: Id) -> &BufferState { + #[cfg(debug_assertions)] + if unsafe { self.buffers.get_unprotected(id.slot) }.is_none() { + std::process::abort(); + } + // SAFETY: Enforced by the caller. unsafe { self.buffers.index_unchecked_unprotected(id.index()) } } @@ -591,6 +596,11 @@ impl Resources { #[inline] pub(crate) unsafe fn image_unchecked_unprotected(&self, id: Id) -> &ImageState { + #[cfg(debug_assertions)] + if unsafe { self.images.get_unprotected(id.slot) }.is_none() { + std::process::abort(); + } + // SAFETY: Enforced by the caller. unsafe { self.images.index_unchecked_unprotected(id.index()) } } @@ -618,6 +628,11 @@ impl Resources { &self, id: Id, ) -> &SwapchainState { + #[cfg(debug_assertions)] + if unsafe { self.swapchains.get_unprotected(id.slot) }.is_none() { + std::process::abort(); + } + // SAFETY: Enforced by the caller. unsafe { self.swapchains.index_unchecked_unprotected(id.index()) } } @@ -1065,7 +1080,7 @@ impl Flight { /// Parameters to create a new [`Resources`] collection. #[derive(Debug)] -pub struct ResourcesCreateInfo { +pub struct ResourcesCreateInfo<'a> { /// The maximum number of [`Buffer`]s that the collection can hold at once. pub max_buffers: u32, @@ -1078,10 +1093,10 @@ pub struct ResourcesCreateInfo { /// The maximum number of [`Flight`]s that the collection can hold at once. pub max_flights: u32, - pub _ne: vulkano::NonExhaustive, + pub _ne: crate::NonExhaustive<'a>, } -impl Default for ResourcesCreateInfo { +impl Default for ResourcesCreateInfo<'_> { #[inline] fn default() -> Self { ResourcesCreateInfo { diff --git a/vulkano/src/pipeline/layout.rs b/vulkano/src/pipeline/layout.rs index f90a3508f1..b94c3e08a6 100644 --- a/vulkano/src/pipeline/layout.rs +++ b/vulkano/src/pipeline/layout.rs @@ -278,8 +278,9 @@ impl PipelineLayout { /// /// The ranges are guaranteed to be sorted deterministically by offset, and /// guaranteed to be disjoint, meaning that there is no overlap between the ranges. + #[doc(hidden)] #[inline] - pub(crate) fn push_constant_ranges_disjoint(&self) -> &[PushConstantRange] { + pub fn push_constant_ranges_disjoint(&self) -> &[PushConstantRange] { &self.push_constant_ranges_disjoint }