Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Synchronization

vk-graph provides a high-performance abstraction over Vulkan synchronization which retains the low driver overhead of correctly synchronized command buffers.

Pipeline Barriers

Vulkan specifies that resources and pipelines will have synchronized access when barriers are inserted into the command stream. Unsynchronized access results in undefined behavior.

Tip

Unsynchronized access may be detected through debug assertions or Vulkan SDK debugging layers.

Access Type Abstraction

vk-graph uses an enumeration of possible states to define all supported pipeline barriers in an easy-to-use way.

Sample access types:

TypeUsage
AccessType::GeneralCovers any access - useful for debug, generally avoid for performance reasons
AccessType::ColorAttachmentWriteWritten as a color attachment during rendering
AccessType::ComputeShaderReadUniformBufferRead as a uniform buffer in a compute shader
AccessType::AccelerationStructureBuildInputReadRead geometry or instance input buffers during AS build/update
AccessType::AccelerationStructureBuildScratchReadWriteRead and write AS build/update scratch storage
AccessType::AccelerationStructureBuildReadRead source or referenced acceleration structures
AccessType::AccelerationStructureBuildWriteWrite destination acceleration structures
AccessType::MicromapBuildInputReadRead encoded opacity or triangle metadata during a micromap build
AccessType::MicromapBuildScratchReadWriteRead and write micromap build scratch storage
AccessType::MicromapBuildWriteWrite a micromap during build, copy, or deserialization
AccessType::MicromapBuildReadRead a micromap during copy, serialization, or property query
AccessType::MicromapBuildBufferReadRead a serialized buffer during micromap deserialization
AccessType::MicromapBuildBufferWriteWrite a serialized buffer during micromap serialization
AccessType::AccelerationStructureBuildMicromapReadRead a completed micromap during a BLAS build

(Full list)

Resource Access

The required access varies depending on the function being called and what the Vulkan specification requires for a given command.

Generally, access must be specified before each command uses a resource. It appears as an “access” function call:

#![allow(unused)]
fn main() {
graph
    .begin_cmd()
    .resource_access(some_buffer, AccessType::TransferRead)
    .resource_access(some_image, AccessType::TransferWrite)
    .record_cmd(|cmd| {
        // we are synchronized!
        // You may:
        //  - Read some_buffer
        //  - Write some_image
    });
}

Resource access is specified for and consumed by the following command buffer recording. For multiple accesses, use multiple “access” and “record” function calls:

#![allow(unused)]
fn main() {
graph
    .begin_cmd()
    .resource_access(buffer, AccessType::TransferRead)
    .resource_access(image, AccessType::TransferWrite)
    .record_cmd(|cmd| {
        // Safe to copy buffer to image
    })
    .resource_access(image, AccessType::TransferRead)
    .resource_access(buffer, AccessType::TransferWrite)
    .record_cmd(|cmd| {
        // Safe to copy image to buffer
    });
}

AS and micromap build structs contain device addresses, which the graph cannot map to resources. Bind and declare the encoded-data, triangle-array, scratch, optional index, destination micromap, and BLAS resources. Put build_micromaps and the consuming BLAS build in separate recordings so the graph can insert the MicromapBuildWrite to AccelerationStructureBuildMicromapRead dependency.

The unsafe build_acceleration_structures and build_acceleration_structures_indirect commands use the same declarations for BUILD and UPDATE. Declare both AS read and write access for in-place updates, and AccessType::AccelerationStructureBuildIndirectRead for indirect AS range buffers. IndirectBuffer maps to DRAW_INDIRECT, not the required ACCELERATION_STRUCTURE_BUILD_KHR / INDIRECT_COMMAND_READ combination. General remains a valid conservative fallback using ALL_COMMANDS with memory read/write access, but may synchronize more work than needed. No dependencies are inserted between entries of a build batch. Borrowed geometry and micromap usage slices must remain alive through recording; resources referenced by address must remain alive through GPU execution. Declarations do not validate address ranges, input contents, build flags, or non-overlap requirements. See the AS command contracts and micromap lifetime rules, including discardable.

Shader Resource Access

When a resource (buffer, image, or acceleration structure) is accessed from a shader the shader_resource_access function is used:

// clear_image.glsl
#version 460 core
#pragma shader_stage(compute)

layout(binding = 42, rgba8) writeonly uniform image2D dstImage;

void main() {
    imageStore(
        dstImage,
        ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y),
        vec4(0)
    );
}
#![allow(unused)]
fn main() {
let mut graph = Graph::default();

let fmt = vk::Format::R8G8B8A8_UNORM;
let usage = vk::ImageUsageFlags::STORAGE;
let info = ImageInfo::image_2d(32, 32, fmt, usage);
let image = graph.bind_resource(Image::create(
    device,
    info,
)?);

graph
    .begin_cmd()
    .bind_pipeline(ComputePipeline::create(
        device,
        ComputePipelineInfo::default(),
        include_bytes!("clear_image.spv").as_slice(),
    )?)
    .shader_resource_access(42, image, AccessType::ComputeShaderWrite)
    .record_cmd(|cmd| {
        cmd.dispatch(32, 32, 1);
    });
}

Subresource Access

Buffer ranges and image views are referred to as subresource ranges and accessed using “subresource” function variants:

#![allow(unused)]
fn main() {
let mut graph = Graph::default();

let fmt = vk::Format::R8G8B8A8_UNORM;
let usage = vk::ImageUsageFlags::STORAGE;
let info = ImageInfo::image_2d(32, 32, fmt, usage);
let image = graph.bind_resource(Image::create(
    device,
    info,
)?);

graph
    .begin_cmd()
    .bind_pipeline(ComputePipeline::create(
        device,
        ComputePipelineInfo::default(),
        include_bytes!("clear_image.spv").as_slice(),
    )?)
    .shader_subresource_access(42, image, info, AccessType::ComputeShaderWrite)
    .record_cmd(|cmd| {
        cmd.dispatch(32, 32, 1);
    });
}

Built-In Commands

The commands directly attached to a Graph, such as Graph::copy_buffer_to_image, do not require any access function calls.

The source code for these built-in commands uses public graph functions and provides good examples of typical usage.