Task graph [5/10]: the new command buffer (#2567)

This commit is contained in:
marc0246 2024-09-20 13:30:36 +02:00 committed by GitHub
parent d89e5cf608
commit bad11eef71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 5933 additions and 268 deletions

10
Cargo.lock generated
View File

@ -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"

View File

@ -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<'_>,
&current_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()
})?;
}

18
examples/bloom/Cargo.toml Normal file
View File

@ -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 }

157
examples/bloom/bloom.rs Normal file
View File

@ -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<ComputePipeline>,
upsample_pipeline: Arc<ComputePipeline>,
bloom_image_id: Id<Image>,
}
impl BloomTask {
pub fn new(rcx: &RenderContext, bloom_image_id: Id<Image>) -> 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: ["."],
}
}

View File

@ -0,0 +1,97 @@
#version 450
#include <shared_exponent.glsl>
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);
}

598
examples/bloom/main.rs Normal file
View File

@ -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::<SceneTask>()
.unwrap()
.handle_resize(&rcx);
task_graph
.task_node_mut(tonemap_node_id)
.unwrap()
.task_mut()
.downcast_mut::<TonemapTask>()
.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::<SceneTask>()
.unwrap()
.cleanup(&rcx);
task_graph
.task_node_mut(tonemap_node_id)
.unwrap()
.task_mut()
.downcast_mut::<TonemapTask>()
.unwrap()
.cleanup(&rcx);
}
_ => (),
}
})
}
pub struct RenderContext {
device: Arc<Device>,
queue: Arc<Queue>,
resources: Arc<Resources>,
flight_id: Id<Flight>,
window: Arc<Window>,
swapchain_id: Id<Swapchain>,
swapchain_format: Format,
bloom_image_id: Id<Image>,
viewport: Viewport,
pipeline_layout: Arc<PipelineLayout>,
descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
sampler: Arc<Sampler>,
descriptor_set: DescriptorSetWithOffsets,
}
impl RenderContext {
fn new(event_loop: &EventLoop<()>, instance: &Arc<Instance>) -> 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<Swapchain>,
pipeline_layout: &Arc<PipelineLayout>,
sampler: &Arc<Sampler>,
descriptor_set_allocator: &Arc<StandardDescriptorSetAllocator>,
) -> (Id<Image>, 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())
}

259
examples/bloom/scene.rs Normal file
View File

@ -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<RenderPass>,
pipeline: Arc<GraphicsPipeline>,
framebuffer: Arc<Framebuffer>,
vertex_buffer_id: Id<Buffer>,
}
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 <shared_exponent.glsl>
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<RenderPass>,
) -> Arc<Framebuffer> {
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()
}

View File

@ -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);
}

View File

@ -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);
}

212
examples/bloom/tonemap.rs Normal file
View File

@ -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<RenderPass>,
pipeline: Arc<GraphicsPipeline>,
framebuffers: Vec<Arc<Framebuffer>>,
swapchain_id: Id<Swapchain>,
}
impl TonemapTask {
pub fn new(rcx: &RenderContext, swapchain_id: Id<Swapchain>) -> 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<RenderPass>,
) -> Vec<Arc<Framebuffer>> {
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::<Vec<_>>()
}

View File

@ -0,0 +1,61 @@
#version 450
#include <shared_exponent.glsl>
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);
}

View File

@ -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<Buffer>,
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<Buffer>,
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<ComputePipeline>,
) -> Result<&mut Self> {
Ok(unsafe { self.bind_pipeline_compute_unchecked(pipeline) })
}
pub unsafe fn bind_pipeline_compute_unchecked(
&mut self,
pipeline: &Arc<ComputePipeline>,
) -> &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<GraphicsPipeline>,
) -> Result<&mut Self> {
Ok(unsafe { self.bind_pipeline_graphics_unchecked(pipeline) })
}
pub unsafe fn bind_pipeline_graphics_unchecked(
&mut self,
pipeline: &Arc<GraphicsPipeline>,
) -> &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<Buffer>],
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<Buffer>],
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::<SmallVec<[_; 2]>>();
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<PipelineLayout>,
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<PipelineLayout>,
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<PipelineLayout>,
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
}
}

View File

@ -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,
&region_vk,
)
};
} else {
let regions_vk = regions
.iter()
.cloned()
.map(vk::ImageSubresourceRange::from)
.collect::<SmallVec<[_; 8]>>();
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,
&region_vk,
)
};
} else {
let regions_vk = regions
.iter()
.cloned()
.map(vk::ImageSubresourceRange::from)
.collect::<SmallVec<[_; 8]>>();
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<Buffer>,
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<Buffer>,
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<Buffer>,
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<Image>,
/// 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<Image>,
/// 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<Buffer>,
/// 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,
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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::<SmallVec<[_; 4]>>();
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<f32>) -> Result<&mut Self> {
Ok(unsafe { self.set_depth_bounds_unchecked(bounds.clone()) })
}
pub unsafe fn set_depth_bounds_unchecked(&mut self, bounds: RangeInclusive<f32>) -> &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::<SmallVec<[_; 2]>>();
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::<SmallVec<[_; 2]>>();
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::<SmallVec<[_; 2]>>();
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::<SmallVec<[_; 2]>>();
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::<SmallVec<[_; 2]>>();
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
}
}

View File

@ -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;

View File

@ -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<Buffer>,
offset: DeviceSize,
) -> Result<&mut Self> {
Ok(unsafe { self.dispatch_indirect_unchecked(buffer, offset) })
}
pub unsafe fn dispatch_indirect_unchecked(
&mut self,
buffer: Id<Buffer>,
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<Buffer>,
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<Buffer>,
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<Buffer>,
offset: DeviceSize,
count_buffer: Id<Buffer>,
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<Buffer>,
offset: DeviceSize,
count_buffer: Id<Buffer>,
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<Buffer>,
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<Buffer>,
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<Buffer>,
offset: DeviceSize,
count_buffer: Id<Buffer>,
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<Buffer>,
offset: DeviceSize,
count_buffer: Id<Buffer>,
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<Buffer>,
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<Buffer>,
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<Buffer>,
offset: DeviceSize,
count_buffer: Id<Buffer>,
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<Buffer>,
offset: DeviceSize,
count_buffer: Id<Buffer>,
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
}
}

View File

@ -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<Buffer>,
/// The byte range of `buffer` to apply the barrier to.
///
/// The default value is empty, which must be overridden.
pub range: Range<DeviceSize>,
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<Image>,
/// 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,
}
}
}

View File

@ -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<Device> {
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<Buffer>) -> &'a Arc<Buffer> {
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<Image>) -> &'a Arc<Image> {
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<T = (), E = Box<vulkano::ValidationError>> = ::std::result::Result<T, E>;

View File

@ -47,9 +47,9 @@ impl<W: ?Sized> TaskGraph<W> {
/// [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<ExecutableTaskGraph<W>, CompileError<W>> {
let CompileInfo {
let &CompileInfo {
queues,
present_queue,
flight_id,
@ -60,7 +60,7 @@ impl<W: ?Sized> TaskGraph<W> {
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<W: ?Sized> TaskGraph<W> {
};
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<W: ?Sized> TaskGraph<W> {
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<W: ?Sized> TaskGraph<W> {
}
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<W: ?Sized> TaskGraph<W> {
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<W: ?Sized> TaskGraph<W> {
unsafe fn queue_family_indices(
&mut self,
device: &Device,
queues: &[Arc<Queue>],
queues: &[&Arc<Queue>],
topological_order: &[NodeIndex],
) -> Result<SmallVec<[u32; 3]>, CompileErrorKind> {
let queue_family_properties = device.physical_device().queue_family_properties();
@ -625,7 +625,7 @@ struct CompileState<'a> {
submissions: Vec<Submission>,
buffer_barriers: Vec<super::BufferMemoryBarrier>,
image_barriers: Vec<super::ImageMemoryBarrier>,
present_queue: Option<Arc<Queue>>,
present_queue: Option<&'a Arc<Queue>>,
initial_buffer_barrier_range: Range<BarrierIndex>,
initial_image_barrier_range: Range<BarrierIndex>,
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<Arc<Queue>>) -> Self {
fn new(prev_accesses: &'a mut [ResourceAccess], present_queue: Option<&'a Arc<Queue>>) -> 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<Queue>) {
fn submit(&mut self, queue: &Arc<Queue>) {
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<W: ?Sized> ExecutableTaskGraph<W> {
///
/// [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<Arc<Queue>>,
pub queues: &'a [&'a Arc<Queue>],
/// 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<Arc<Queue>>,
pub present_queue: Option<&'a Arc<Queue>>,
/// The flight which will be executed.
///
/// The default value is `Id::INVALID`, which must be overridden.
pub flight_id: Id<Flight>,
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::<Vec<_>>(),
..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::<Vec<_>>(),
..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::<Vec<_>>(),
..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::<Vec<_>>(),
..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::<Vec<_>>(),
..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::<Vec<_>>(),
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::<Vec<_>>(),
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,

View File

@ -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<W: ?Sized + 'static> ExecutableTaskGraph<W> {
/// - 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<W: ?Sized + 'static> ExecutableTaskGraph<W> {
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<W: ?Sized + 'static> ExecutableTaskGraph<W> {
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<W: ?Sized + 'static> ExecutableTaskGraph<W> {
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<W: ?Sized + 'static> ExecutableTaskGraph<W> {
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<BarrierIndex>,
@ -690,7 +683,7 @@ impl<'a, W: ?Sized + 'static> ExecuteState2<'a, W> {
fn convert_initial_buffer_barriers(&mut self, barrier_range: Range<BarrierIndex>) {
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<BarrierIndex>) {
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<BarrierIndex>,
image_barrier_range: Range<BarrierIndex>,
) {
) -> 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<BarrierIndex>) {
@ -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<BarrierIndex>,
@ -1113,7 +1104,7 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> {
fn convert_initial_buffer_barriers(&mut self, barrier_range: Range<BarrierIndex>) {
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<BarrierIndex>) {
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<BarrierIndex>,
image_barrier_range: Range<BarrierIndex>,
) {
) -> 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<BarrierIndex>) {
@ -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,
&current_submission!($state).queue,
)?);
}
$state.current_command_buffer.as_mut().unwrap()
}};
}
use current_command_buffer;
fn create_command_buffer(
resource_map: &ResourceMap<'_>,
queue: &Queue,
) -> Result<RawRecordingCommandBuffer, VulkanError> {
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<Buffer>) -> &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<Image>) -> &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<Swapchain>) -> &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) };

View File

@ -72,7 +72,7 @@ impl<W: ?Sized> TaskGraph<W> {
/// maximum number of virtual resources the graph can ever have.
#[must_use]
pub fn new(
physical_resources: Arc<resource::Resources>,
physical_resources: &Arc<resource::Resources>,
max_nodes: u32,
max_resources: u32,
) -> Self {
@ -82,7 +82,7 @@ impl<W: ?Sized> TaskGraph<W> {
},
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<W: ?Sized> TaskNode<W> {
fn new(queue_family_type: QueueFamilyType, task: impl Task<World = W>) -> 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<W: ?Sized> TaskNode<W> {
}
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)

View File

@ -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<Queue>,
resources: Arc<Resources>,
queue: &Arc<Queue>,
resources: &Arc<Resources>,
flight_id: Id<Flight>,
task: impl FnOnce(&mut RawRecordingCommandBuffer, &mut TaskContext<'_>) -> TaskResult,
task: impl FnOnce(&mut RecordingCommandBuffer<'_>, &mut TaskContext<'_>) -> TaskResult,
host_buffer_accesses: impl IntoIterator<Item = (Id<Buffer>, HostAccessType)>,
buffer_accesses: impl IntoIterator<Item = (Id<Buffer>, AccessType)>,
image_accesses: impl IntoIterator<Item = (Id<Image>, 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<W: ?Sized + 'static> Task for PhantomData<fn() -> W> {
unsafe fn execute(
&self,
_cbf: &mut RawRecordingCommandBuffer,
_cbf: &mut RecordingCommandBuffer<'_>,
_tcx: &mut TaskContext<'_>,
_world: &Self::World,
) -> TaskResult {
@ -223,12 +225,11 @@ impl<W: ?Sized + 'static> Task for PhantomData<fn() -> 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<Option<&'a mut DeathRow>>,
current_frame_index: u32,
command_buffers: Cell<Option<&'a mut Vec<Arc<RawCommandBuffer>>>>,
command_buffers: &'a mut Vec<Arc<RawCommandBuffer>>,
}
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<Buffer>) -> 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<Image>) -> 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<Swapchain>) -> 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<RawCommandBuffer>) {
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<RawCommandBuffer>) {
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<Item = Arc<RawCommandBuffer>>,
) {
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::<Vec<_>>(),
)
}};

View File

@ -121,9 +121,9 @@ impl Resources {
///
/// - Panics if `device` already has a `Resources` collection associated with it.
#[must_use]
pub fn new(device: Arc<Device>, create_info: ResourcesCreateInfo) -> Arc<Self> {
pub fn new(device: &Arc<Device>, create_info: &ResourcesCreateInfo<'_>) -> Arc<Self> {
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<Buffer>) -> &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<Image>) -> &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<Swapchain>,
) -> &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 {

View File

@ -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
}