Expand description

This crate provides RAII helpers for ash. In particular:

Introduction

When working with Vulkan, you generally want to group multiple resources together into a few large structs. Unfortunately, errors during initialization can leak resources:

unsafe fn create_resources(device: &ash::Device) -> VkResult<Resources> {
    let render_pass = create_render_pass(device)?;

    // BUG: Failure leaks render_pass
    let pipeline_layout = create_pipeline_layout(device)?;

    // BUG: Failure leaks render_pass and pipeline_layout
    let pipeline = create_pipeline(device, render_pass, pipeline_layout)?;

    Ok(Resources {
        render_pass,
        pipeline_layout,
        pipeline,
    })
}

It’s straightforward to solve this with scopeguard, but it tends to be a bit verbose and repetitive:

use scopeguard::ScopeGuard;

type Guarded<'a, T> = ScopeGuard<(T, &'a ash::Device), fn((T, &'a ash::Device))>;

unsafe fn create_resources(device: &ash::Device) -> VkResult<Resources> {
    let render_pass = create_render_pass(device)?;
    let pipeline_layout = create_pipeline_layout(device)?;
    let pipeline = create_pipeline(device, render_pass.0, pipeline_layout.0)?;
    Ok(Resources {
        render_pass: ScopeGuard::into_inner(render_pass).0,
        pipeline_layout: ScopeGuard::into_inner(pipeline_layout).0,
        pipeline: ScopeGuard::into_inner(pipeline).0,
    })
}

unsafe fn create_render_pass(device: &ash::Device) -> VkResult<Guarded<vk::RenderPass>> {
    let create_info = unimplemented!();
    let render_pass = device.create_render_pass(create_info, None)?;
    Ok(scopeguard::guard(
        (render_pass, device),
        |(render_pass, device)| device.destroy_render_pass(render_pass, None),
    ))
}

// fn create_pipeline_layout(...) { ... }
// fn create_pipeline(...) { ... }

ashpan reduces the friction of using scopeguard with ash by automatically selecting the destructor, passing the same allocation_callbacks to the destructor that were used for resource creation and making guarded resources convenient to extract:

use ashpan::{DeviceExt, Guarded};

unsafe fn create_resources(device: &ash::Device) -> VkResult<Resources> {
    let render_pass = create_render_pass(device)?;
    let pipeline_layout = create_pipeline_layout(device)?;
    let pipeline = create_pipeline(device, *render_pass, *pipeline_layout)?;
    Ok(Resources {
        render_pass: render_pass.take(),
        pipeline_layout: pipeline_layout.take(),
        pipeline: pipeline.take(),
    })
}

unsafe fn create_render_pass(device: &ash::Device) -> VkResult<Guarded<vk::RenderPass>> {
    let create_info = unimplemented!();
    device.create_guarded_render_pass(create_info, None)
}

// fn create_pipeline_layout(...) { ... }
// fn create_pipeline(...) { ... }

It’s also possible to extend Guarded to handle application-specific types:

use ashpan::{Destroyable, DeviceExt, Guarded};

impl Destroyable for Resources {
    type Destroyer = ash::Device;

    unsafe fn destroy_with(
        &mut self,
        device: &ash::Device,
        allocation_callbacks: Option<&vk::AllocationCallbacks>,
    ) {
        device.destroy_pipeline(self.pipeline, allocation_callbacks);
        device.destroy_pipeline_layout(self.pipeline_layout, allocation_callbacks);
        device.destroy_render_pass(self.render_pass, allocation_callbacks);
    }
}

// Elsewhere...
unsafe fn create_resources(device: &ash::Device) -> VkResult<Guarded<Resources>> {
    let resources = unimplemented!();
    Ok(Guarded::new(resources, device, None))
}

Structs

ScopeGuard tailored for Vulkan

Traits

Indicates that a type is destroyable

Extension trait adding guarded methods to ash::Device

Extension trait adding guarded methods to ash::Entry

Extension trait adding guarded methods to ash::Instance

Type Definitions

Most common usecase for GuardedResource