Another basic question about example with lifetime parameter

I have some code, probably from WGPU tutorial, that calls Device::create_render_pipeline. The argument there (RenderPipelineDescriptor<'_>) appears to have a lifetime parameter.

Part of that descriptor is a VertexState<'a> struct, which in turn has an slice of VertexBufferLayout<'a>. My code that created the descriptor looked like:

let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
    label: Some("Render Pipeline"),
    layout: Some(&render_pipeline_layout),
    vertex: wgpu::VertexState {
        module: &shader,
        entry_point: Some("vs_main"),
        buffers: &[ Vertex::desc(), InstanceData::desc() ],
        ...

Vertex and InstanceData are my own structs, that describe data sent to shader. The desc() functions used to look like:

impl InstanceData {
    pub fn desc() -> wgpu::VertexBufferLayout<'static> {
        use std::mem;
        wgpu::VertexBufferLayout {
            array_stride: size_of::<InstanceData>() as wgpu::BufferAddress,
            step_mode: wgpu::VertexStepMode::Instance,
            attributes: &[                
                wgpu::VertexAttribute {
                    offset: 0,
                    shader_location: 2,
                    format: wgpu::VertexFormat::Float32x4,
                },
                ...

I never really noticed it, because the code was copied from a tutorial, but I guess that function was creating an array of VertexAttribute and returning a ref/slice to it as 'static, and it got away with this because everything in the array was constant/static. So it wasn't really created afresh in each call to desc().

But now, I want to change desc() to take a parameter, as shown below, so I can use it in different shaders where the first field @location is different.

pub fn desc(start_location: u32) -> wgpu::VertexBufferLayout<'static> {
    ...
    attributes: &[
        wgpu::VertexAttribute {
           offset: 0,
           shader_location: start_location, 
           ...
           shader_location: start_location+1, 
           ...

And the code above that calls desc(...) would now pass a u32:

let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
    ...
    buffers: &[ Vertex::desc(), InstanceData::desc(3) ],

Now it won't compile, as the array can no longer be 'static. That makes sense.

But I'm not sure how I should refactor everything here. If I'm understanding things right, the RenderPipelineDescriptor has a lifetime parameter because it has references inside it. So anything not 'static I need to create and own in that function that calls create_render_pipeline. Any pointers on how to do this? (Maybe I need to give more info about the surrounding code?)

If you're only going to have a handful of start_location values, you could use const generics instead.

pub fn desc<const START_LOCATION: u32>() -> wgpu::VertexBufferLayout<'static> {
1 Like

these references are all configuration states. typically the rendering pipelines are static, they can be hand-crafted (or mechanically generated) at compile time. if your pipeline configurations are dynamic, e.g. you compile your shaders at runtime and use shader reflection to validate the configurations at runtime, these states should be owned by the return value of whatever shader refection library, in which case, the various desc() functions can be implemented as methods of the refection data (essentially, you create your own pipeline "descriptor" type, and convert that to the descriptor needed by the wgpu API).

here's a sketch:

struct ShaderReflectionInfo {
    vertex_entry_point: String,
    fragment_entry_point: String,
    vertex_attributes: Vec<VertexAtributes>,
    //...
}
impl ShaderReflectionInfo {
    fn analyze_spirv(vs: &[u32], fs: &[u32]) -> Result<Self> { todo!() }
    fn vertex_state(&self) -> VertexState<'_> { todo!() }
    fn fragment_state(&self) -> FragmentState<'_> { todo!() }
    //...
    // or even just
    fn pipeline_desc(&self) -> RenderPipelineDescriptor<'_> { todo!() }
}
fn main () {
    //...
    let vs = compile_shader('shader.vertex');
    let fs = compile_shader('fragment.vertex');
    let info = ShaderReflectionInfo::analyze_shader(vs.raw_sprv(), fs.raw_sprv());
    let pipeline = device.create_render_pipeline(info.pipeline_desc());
    //...
}
1 Like

Isn't the main problem here the fact that you return a reference to an array containing start_location, but which is a temporary value because it only lives in the function desc? The array reference you give to the attributes field cannot be static or even given a lifetime larger than the function's. It's the array reference you should give in argument.

If that attribute array was stored elsewhere with a lifetime 'a, you should be able to use a

pub fn desc<'a>(attributes: &'a[wgu::VertexAttribute]) -> wgpu::VertexBufferLayout<'a>

(or 'static if you have a series of static/constant arrays you point at)

Which can be elided as

pub fn desc(attributes: &[wgu::VertexAttribute]) -> wgpu::VertexBufferLayout

I'm not familiar with that framework, so I don't know if that's a convenient way to work with it. The alternative is to use only static or constant values that don't depend on a parameter.