ruststructscopevulkanash

Members of struct lose value when created by factory method


I hope that I'm able to explain my problem as clear as possible. I'm working on a Rust application that uses Vulkan.

I ran into some trouble creating the graphics pipeline. I found the issue, but I have no idea what and why it is happening.

First let me show what works. For creating the graphics pipelines I need to create something called a PipelineViewportStateCreateInfo. This struct (and some others) I then use to create a GraphicsPipelineCreateInfo.

The width and height properties of swapchain_extent are set to 1600 and 1200 respectively.

    let viewports = [Viewport::builder()
        .x(0.0)
        .y(0.0)
        .width(swapchain_extent.width as f32)
        .height(swapchain_extent.height as f32)
        .min_depth(0.0)
        .max_depth(0.0)
        .build()];

    let scissors = [Rect2D::builder()
        .offset(Offset2D::builder().x(0).y(0).build())
        .extent(swapchain_extent)
        .build()];

    let viewport_state_create_info = PipelineViewportStateCreateInfo::builder()
        .scissors(&scissors)
        .viewports(&viewports)
        .build();

    let graphics_pipeline_create_infos = [GraphicsPipelineCreateInfo::builder()
        .viewport_state(&viewport_state_create_info)
        .build()];

This all works perfectly.

To make my code more structured I created a helper function that takes care of creating the PipelineViewPortStateCreateInfo:

fn create_viewport_state_create_info(extent: Extent2D) -> PipelineViewportStateCreateInfo {
    let viewports = [Viewport::builder()
        .x(0.0)
        .y(0.0)
        .width(extent.width as f32)
        .height(extent.height as f32)
        .min_depth(0.0)
        .max_depth(0.0)
        .build()];

    let scissors = [Rect2D::builder()
        .offset(Offset2D::builder().x(0).y(0).build())
        .extent(extent)
        .build()];

    PipelineViewportStateCreateInfo::builder()
        .viewports(&viewports)
        .scissors(&scissors)
        .build()
}

And I call it from my other function like this:

    let viewport_state_create_info = create_viewport_state_create_info(swapchain_extent);

    let graphics_pipeline_create_infos = [GraphicsPipelineCreateInfo::builder()
        .viewport_state(&viewport_state_create_info)
        .build()];

I don't see a problem with this code. However when I run this code the Vulkan validation layer throws this error back at me:

VALIDATION [VUID-VkViewport-width-01770 (-1542042715)]: Validation Error: [ VUID-VkViewport-width-01770 ] | MessageID = 0xa4164ba5 | vkCreateGraphicsPipelines(): pCreateInfos[0].pViewportState->pViewports[0].width (0.000000) is not greater than 0.0. The Vulkan spec states: width must be greater than 0.0

Even though swapchain_extent.width is set to 1600.

In the debugger I see that it is indeed 1600, but when I step into the build function of the GraphicsPipelineCreateInfoBuilder suddenly both width and height become 0.0 🤔

Is this some scoping issue? If it is, isn't the compiler supposed to protect me against it?


Solution

  • Yes, you are correct in your assumption. It is a scoping issue.

    The issue is that while PipelineViewportStateCreateInfoBuilder is bound by 'a and references viewports and scissors. The issue is that PipelineViewportStateCreateInfo does not, and instead keeps pointers (not references) to viewports and scissors.

    #[repr(C)]
    pub struct PipelineViewportStateCreateInfo {
        ...
        pub p_viewports: *const Viewport,
        pub p_scissors: *const Rect2D,
        ...
    }
    

    So the second that create_viewport_state_create_info() returns, then both viewports and scissors are dropped. So the pointers in PipelineViewportStateCreateInfo are now invalid.

    The reason that the compiler isn't able to catch this. Is because when build() is called, it returns a PipelineViewportStateCreateInfo which is not bound by any lifetime anymore. It could in theory. However, ash is simply mirroring the definition of VkPipelineViewportStateCreateInfo.

    The PipelineViewportStateCreateInfoBuilder is actually doing exactly what you would do in that case. It contains a PipelineViewportStateCreateInfo and uses a PhantomData for the unused lifetime.

    #[repr(transparent)]
    pub struct PipelineViewportStateCreateInfoBuilder<'a> {
        inner: PipelineViewportStateCreateInfo,
        marker: ::std::marker::PhantomData<&'a ()>,
    }