1. 程式人生 > >以OpenGL/ES視角介紹gfx-hal(Vulkan) Shader/Program介面使用

以OpenGL/ES視角介紹gfx-hal(Vulkan) Shader/Program介面使用

文件列表見:Rust 移動端跨平臺複雜圖形渲染專案開發系列總結(目錄)

【草稿】

接下來的故事圍繞RawCommandBuffer定義的兩個核心方法展開:

bind_graphics_pipeline(&GraphicsPipeline)
bind_graphics_descriptor_sets(PipelineLayout, DescriptorSet)
複製程式碼

函式原型:

/// Bind a graphics pipeline.
///
/// # Errors
///
/// This function does not return an error. Invalid usage of this function
/// will result in an error on `finish`. /// /// - Command buffer must be in recording state. /// - Only queues with graphics capability support this function. fn bind_graphics_pipeline(&mut self, pipeline: &B::GraphicsPipeline); /// Takes an iterator of graphics `DescriptorSet`'s, and binds them to the command buffer.
/// `first_set` is the index that the first descriptor is mapped to in the command buffer. fn bind_graphics_descriptor_sets<I, J>( &mut self, layout: &B::PipelineLayout, first_set: usize, sets: I, offsets: J, ) where I: IntoIterator, I::Item: Borrow<B::DescriptorSet>, J: IntoIterator
, J::Item: Borrow<DescriptorSetOffset>; 複製程式碼

這兩個方法涉及三個重要資料結構:GraphicsPipeline、PipelineLayout、DescriptorSet,它們的建立順序是相反的,從後到前。下面逐一介紹。

DescriptorSet

初始化流程如下:

  1. 用pso::DescriptorSetLayoutBinding分別描述Shader宣告的Uniform變數並組成陣列,比如texture2D、sampler和UniformBlock中的每個變數。
  2. 傳遞前面的pso::DescriptorSetLayoutBinding陣列到Device建立DescriptorSetLayout。
  3. 用pso::DescriptorRangeDesc彙總描述Shader宣告的Set數量與所有Uniform變數並組成陣列。
  4. 傳遞前面的pso::DescriptorRangeDesc陣列到Device建立DescriptorPool。
  5. 傳遞前面的DescriptorSetLayout到DescriptorPool建立DescriptorSet,此時DescriptorSet並無實際資料
  6. 通過Device寫入實際資料到DescriptorSet

DescriptorSet初始化流程示例

假設Fragment Shader定義如下uniform變數:

layout(set = 0, binding = 0) uniform texture2D u_texture;
layout(set = 0, binding = 1) uniform sampler u_sampler;

layout(set = 0, binding = 2) uniform texture2D u_texture2;
layout(set = 0, binding = 3) uniform sampler u_sampler2;

layout(set = 0, binding = 4) uniform UBOCol {
    vec4 color;
} color_dat;
複製程式碼

那麼,對應的DescriptorSetLayout和DescriptorSetLayoutBinding為:

let set_layout = device
    .create_descriptor_set_layout(
        &[
            pso::DescriptorSetLayoutBinding {
                binding: 0,
                ty: pso::DescriptorType::SampledImage,
                count: 1,
                stage_flags: ShaderStageFlags::FRAGMENT,
            },
            pso::DescriptorSetLayoutBinding {
                binding: 1,
                ty: pso::DescriptorType::Sampler,
                count: 1,
                stage_flags: ShaderStageFlags::FRAGMENT,
            },
            pso::DescriptorSetLayoutBinding {
                binding: 2,
                ty: pso::DescriptorType::SampledImage,
                count: 1,
                stage_flags: ShaderStageFlags::FRAGMENT,
            },
            pso::DescriptorSetLayoutBinding {
                binding: 3,
                ty: pso::DescriptorType::Sampler,
                count: 1,
                stage_flags: ShaderStageFlags::FRAGMENT,
            },
            pso::DescriptorSetLayoutBinding {
                binding: 4,
                ty: pso::DescriptorType::UniformBuffer,
                count: 1,
                stage_flags: ShaderStageFlags::FRAGMENT,
            },
        ],
        &[], // Ignore immutable_samplers
    )
    .expect("Can't create descriptor set layout");

let mut desc_pool = device
    .create_descriptor_pool(
        1, // sets
        &[
            pso::DescriptorRangeDesc {
                ty: pso::DescriptorType::SampledImage,
                count: 2,
            },
            pso::DescriptorRangeDesc {
                ty: pso::DescriptorType::Sampler,
                count: 2,
            },
            pso::DescriptorRangeDesc {
                ty: pso::DescriptorType::UniformBuffer,
                count: 1,
            },
        ],
    )
    .expect("Can't create descriptor pool");
// 分配資源
let desc_set/* B::DescriptorSet */ = desc_pool.allocate_set(&set_layout).unwrap();
// 寫入實際資料
device.write_descriptor_sets(vec![
    pso::DescriptorSetWrite {
        set: &desc_set,
        binding: 0,
        array_offset: 0,
        descriptors: Some(pso::Descriptor::Image(&image_srv, image::Layout::Undefined)),
    },
    pso::DescriptorSetWrite {
        set: &desc_set,
        binding: 1,
        array_offset: 0,
        descriptors: Some(pso::Descriptor::Sampler(&sampler)),
    },
    pso::DescriptorSetWrite {
        set: &desc_set,
        binding: 2,
        array_offset: 0,
        descriptors: Some(pso::Descriptor::Image(&image_srv2, image::Layout::Undefined)),
    },
    pso::DescriptorSetWrite {
        set: &desc_set,
        binding: 3,
        array_offset: 0,
        descriptors: Some(pso::Descriptor::Sampler(&sampler2)),
    },    
    pso::DescriptorSetWrite {
        set: &desc_set,
        binding: 4,
        array_offset: 0,
        descriptors: Some(pso::Descriptor::Buffer(&uniform_buffer, Some(0)..Some(1))),
    },
]);
複製程式碼

相關操作的函式原型

/// Create a descriptor set layout.
///
/// A descriptor set layout object is defined by an array of zero or more descriptor bindings.
/// Each individual descriptor binding is specified by a descriptor type, a count (array size)
/// of the number of descriptors in the binding, a set of shader stages that **can** access the
/// binding, and (if using immutable samplers) an array of sampler descriptors.
fn create_descriptor_set_layout<I, J>(
    &self,
    bindings: I,
    immutable_samplers: J,
) -> Result<B::DescriptorSetLayout, OutOfMemory>
where
    I: IntoIterator,
    I::Item: Borrow<pso::DescriptorSetLayoutBinding>,
    J: IntoIterator,
    J::Item: Borrow<B::Sampler>;

/// Create a descriptor pool.
///
/// Descriptor pools allow allocation of descriptor sets.
/// The pool can't be modified directly, only through updating descriptor sets.
fn create_descriptor_pool<I>(&self, max_sets: usize, descriptor_ranges: I) -> Result<B::DescriptorPool, OutOfMemory>
where
    I: IntoIterator,
    I::Item: Borrow<pso::DescriptorRangeDesc>;

/// Allocate a descriptor set from the pool.
///
/// The descriptor set will be allocated from the pool according to the corresponding set layout. However,
/// specific descriptors must still be written to the set before use using a [`DescriptorSetWrite`] or
/// [`DescriptorSetCopy`].
/// 
/// Descriptors will become invalid once the pool is reset. Usage of invalidated descriptor sets results
/// in undefined behavior.
/// 
/// [`DescriptorSetWrite`]: struct.DescriptorSetWrite.html
/// [`DescriptorSetCopy`]: struct.DescriptorSetCopy.html
fn allocate_set(&mut self, layout: &B::DescriptorSetLayout) -> Result<B::DescriptorSet, AllocationError> {
    let mut sets = Vec::with_capacity(1);
    self.allocate_sets(Some(layout), &mut sets)
        .map(|_| sets.remove(0))
}

/// Allocate one or multiple descriptor sets from the pool.
///
/// The descriptor set will be allocated from the pool according to the corresponding set layout. However,
/// specific descriptors must still be written to the set before use using a [`DescriptorSetWrite`] or
/// [`DescriptorSetCopy`].
/// 
/// Each descriptor set will be allocated from the pool according to the corresponding set layout.
/// Descriptors will become invalid once the pool is reset. Usage of invalidated descriptor sets results
/// in undefined behavior.
/// 
/// [`DescriptorSetWrite`]: struct.DescriptorSetWrite.html
/// [`DescriptorSetCopy`]: struct.DescriptorSetCopy.html
fn allocate_sets<I>(&mut self, layouts: I, sets: &mut Vec<B::DescriptorSet>) -> Result<(), AllocationError>
where
    I: IntoIterator,
    I::Item: Borrow<B::DescriptorSetLayout>,
{
    let base = sets.len();
    for layout in layouts {
        match self.allocate_set(layout.borrow()) {
            Ok(set) => sets.push(set),
            Err(e) => {
                self.free_sets(sets.drain(base ..));
                return Err(e)
            }
        }
    }
    Ok(())
}

/// Specifying the parameters of a descriptor set write operation
fn write_descriptor_sets<'a, I, J>(&self, write_iter: I)
where
    I: IntoIterator<Item = pso::DescriptorSetWrite<'a, B, J>>,
    J: IntoIterator,
    J::Item: Borrow<pso::Descriptor<'a, B>>;
複製程式碼

DescriptorSet相關資料結構定義

DescriptorSetLayout定義

A descriptor set layout object is defined by an array of zero or more descriptor bindings. Each individual descriptor binding is specified by a descriptor type, a count (array size) of the number of descriptors in the binding, a set of shader stages that can access the binding, and (if using immutable samplers) an array of sampler descriptors.

www.khronos.org/registry/vu…

DescriptorSetLayoutBinding定義

Structure specifying a descriptor set layout binding

www.khronos.org/registry/vu…

Immutable Samplers定義

todo

DescriptorSetWrite

/// Writes the actual descriptors to be bound into a descriptor set. Should be provided
/// to the `write_descriptor_sets` method of a `Device`.
#[allow(missing_docs)]
pub struct DescriptorSetWrite<'a, B: Backend, WI>
    where WI: IntoIterator,
          WI::Item: Borrow<Descriptor<'a, B>>
{
    pub set: &'a B::DescriptorSet,
    /// *Note*: when there is more descriptors provided than
    /// array elements left in the specified binding starting
    /// at specified, offset, the updates are spilled onto
    /// the next binding (starting with offset 0), and so on.
    pub binding: DescriptorBinding,
    pub array_offset: DescriptorArrayIndex,
    pub descriptors: WI,
}
複製程式碼

PipelineLayout

初始化流程如下:

  1. 由前面建立的DescriptorSetLayout + pso::ShaderStageFlags向Device申請建立PipelineLayout例項。

PipelineLayout初始化流程示例

let pipeline_layout = device
    .create_pipeline_layout(
        std::iter::once(&set_layout),
        &[(pso::ShaderStageFlags::VERTEX, 0..8)],
    )
    .expect("Can't create pipeline layout");
複製程式碼

相關操作的函式原型

/// Create a new pipeline layout object.
///
/// # Arguments
///
/// * `set_layouts` - Descriptor set layouts
/// * `push_constants` - Ranges of push constants. A shader stage may only contain one push
///     constant block. The length of the range indicates the number of u32 constants occupied
///     by the push constant block.
///
/// # PipelineLayout
///
/// Access to descriptor sets from a pipeline is accomplished through a *pipeline layout*.
/// Zero or more descriptor set layouts and zero or more push constant ranges are combined to
/// form a pipeline layout object which describes the complete set of resources that **can** be
/// accessed by a pipeline. The pipeline layout represents a sequence of descriptor sets with
/// each having a specific layout. This sequence of layouts is used to determine the interface
/// between shader stages and shader resources. Each pipeline is created using a pipeline layout.
fn create_pipeline_layout<IS, IR>(
    &self,
    set_layouts: IS,
    push_constant: IR,
) -> Result<B::PipelineLayout, OutOfMemory>
where
    IS: IntoIterator,
    IS::Item: Borrow<B::DescriptorSetLayout>,
    IR: IntoIterator,
    IR::Item: Borrow<(pso::ShaderStageFlags, Range<u32>)>;
複製程式碼

PipelineLayout相關資料結構定義

PipelineLayout定義

Access to descriptor sets from a pipeline is accomplished through a pipeline layout. Zero or more descriptor set layouts and zero or more push constant ranges are combined to form a pipeline layout object which describes the complete set of resources that can be accessed by a pipeline. The pipeline layout represents a sequence of descriptor sets with each having a specific layout. This sequence of layouts is used to determine the interface between shader stages and shader resources. Each pipeline is created using a pipeline layout.

www.khronos.org/registry/vu…

GraphicsPipeline

初始化流程如下:

todo