Vulkan ( gfx-hal ) 實現 OpenGL / ES Shader / Program

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

2019.6.17 變動:修改標題數組

背景:數據結構

The right way to tackle this in Vulkan is to use resource descriptors. A descriptor is a way for shaders to freely access resources like buffers and images. Usage of descriptors consists of three parts:app

  • Specify a descriptor layout during pipeline creation
  • Allocate a descriptor set from a descriptor pool
  • Bind the descriptor set during rendering

The descriptor layout specifies the types of resources that are going to be accessed by the pipeline, just like a render pass specifies the types of attachments that will be accessed. A descriptor set specifies the actual buffer or image resources that will be bound to the descriptors, just like a framebuffer specifies the actual image views to bind to render pass attachments. The descriptor set is then bound for the drawing commands just like the vertex buffers and framebuffer.dom

Copy the data to a VkBuffer and access it through a uniform buffer object descriptor from the vertex shader.ide

Chapter 25 Descriptor layout and buffer函數

Vulkan Tutorialpost

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

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

函數原型:this

/// 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

初始化流程以下:

  1. 初始化RenderPass
  2. 由SPIR-V建立ShaderModule
  3. 由ShaderModule建立EntryPoint
  4. 由EntryPoint建立GraphicsShaderSet
  5. 由RenderPass建立Subpass
  6. 初始化GraphicsPipelineDesc
  7. 經過GraphicsPipelineDesc建立GraphicsPipeline

GraphicsPipeline初始化流程示例

// 由SPIR-V建立ShaderModule
let vs_module = device.create_shader_module(&vertex_spirv).unwrap();
let fs_module = device.create_shader_module(&fragment_spirv).unwrap();
const ENTRY_NAME: &str = "main";
// 建立EntryPoint
let (vs_entry, fs_entry) = (
    pso::EntryPoint {
        entry: ENTRY_NAME,
        module: &vs_module,
        specialization: &[
            Specialization {
                id: 0,
                value: pso::Constant::F32(0.8),
            }
        ],
    },
    pso::EntryPoint {
        entry: ENTRY_NAME,
        module: &fs_module,
        specialization: &[],
    },
);
// 建立GraphicsShaderSet
let shader_entries = pso::GraphicsShaderSet {
    vertex: vs_entry,
    hull: None,
    domain: None,
    geometry: None,
    fragment: Some(fs_entry),
};
// 建立Subpass
let subpass = Subpass { index: 0, main_pass: &render_pass };
// 建立GraphicsPipelineDesc
let mut pipeline_desc = pso::GraphicsPipelineDesc::new(
    shader_entries,
    Primitive::TriangleList,
    pso::Rasterizer::FILL,
    &pipeline_layout,
    subpass,
);
pipeline_desc.blender.targets.push(pso::ColorBlendDesc(
    pso::ColorMask::ALL,
    pso::BlendState::ALPHA,
));
pipeline_desc.vertex_buffers.push(pso::VertexBufferDesc {
    binding: 0,
    stride: std::mem::size_of::<Vertex>() as u32,
    rate: 0,
});
pipeline_desc.attributes.push(pso::AttributeDesc {
    location: 0,
    binding: 0,
    element: pso::Element {
        format: format::Format::Rg32Float,
        offset: 0,
    },
});
pipeline_desc.attributes.push(pso::AttributeDesc {
    location: 1,
    binding: 0,
    element: pso::Element {
        format: format::Format::Rg32Float,
        offset: 8
    },
});
// 經過GraphicsPipelineDesc建立GraphicsPipeline
let pipeline = device.create_graphics_pipeline(&pipeline_desc)
複製代碼

GraphicsPipeline相關操做的函數原型

/// Create a new shader module object through the SPIR-V binary data.
///
/// Once a shader module has been created, any entry points it contains can be used in pipeline
/// shader stages as described in *Compute Pipelines* and *Graphics Pipelines*.
fn create_shader_module(&self, spirv_data: &[u8]) -> Result<B::ShaderModule, ShaderError>;
複製代碼

GraphicsPipeline相關數據結構定義

Shader定義

Shader modules contain shader code and one or more entry points. Shaders are selected from a shader module by specifying an entry point as part of pipeline creation. The stages of a pipeline can use shaders that come from different modules. The shader code defining a shader module must be in the SPIR-V format, as described by the Vulkan Environment for SPIR-V appendix.

www.khronos.org/registry/vu…

type ShaderModule:        fmt::Debug + Any + Send + Sync;
複製代碼

EntryPoint定義

/// Shader entry point.
#[derive(Debug, Copy)]
pub struct EntryPoint<'a, B: Backend> {
    /// Entry point name.
    pub entry: &'a str,
    /// Shader module reference.
    pub module: &'a B::ShaderModule,
    /// Specialization info.
    pub specialization: &'a [Specialization],
}
複製代碼

Specialization定義

/// Specialization information for pipelines.
/// 
/// Specialization constants allow for easy configuration of 
/// multiple similar pipelines. For example, there may be a 
/// boolean exposed to the shader that switches the specularity on/off
/// provided via a specialization constant.
/// That would produce separate PSO's for the "on" and "off" states 
/// but they share most of the internal stuff and are fast to produce. 
/// More importantly, they are fast to execute, since the driver 
/// can optimize out the branch on that other PSO creation.
#[derive(Debug, Clone)]
pub struct Specialization {
    /// Constant identifier in shader source.
    pub id: u32,
    /// Value to override specialization constant.
    pub value: Constant,
}
複製代碼

GraphicsShaderSet定義

/// A complete set of shaders to build a graphics pipeline.
///
/// All except the vertex shader are optional; omitting them
/// passes through the inputs without change.
///
/// If a fragment shader is omitted, the results of fragment
/// processing are undefined. Specifically, any fragment color
/// outputs are considered to have undefined values, and the
/// fragment depth is considered to be unmodified. This can
/// be useful for depth-only rendering.
#[derive(Clone, Debug)]
pub struct GraphicsShaderSet<'a, B: Backend> {
    /// A shader that outputs a vertex in a model.
    pub vertex: EntryPoint<'a, B>,
    /// A hull shader takes in an input patch (values representing
    /// a small portion of a shape, which may be actual geometry or may
    /// be parameters for creating geometry) and produces one or more
    /// output patches.
    pub hull: Option<EntryPoint<'a, B>>,
    /// A shader that takes in domains produced from a hull shader's output
    /// patches and computes actual vertex positions.
    pub domain: Option<EntryPoint<'a, B>>,
    /// A shader that takes given input vertexes and outputs zero
    /// or more output vertexes.
    pub geometry: Option<EntryPoint<'a, B>>,
    /// A shader that outputs a value for a fragment.
    /// Usually this value is a color that is then displayed as a
    /// pixel on a screen.
    pub fragment: Option<EntryPoint<'a, B>>,
}
複製代碼

RenderPass定義

A render pass represents a collection of attachments, subpasses, and dependencies between the subpasses, and describes how the attachments are used over the course of the subpasses. The use of a render pass in a command buffer is a render pass instance.

www.khronos.org/registry/vu…

理解Subpass

Understanding subpasses

GraphicsPipelineDesc定義

/// A description of all the settings that can be altered
/// when creating a graphics pipeline.
#[derive(Debug)]
pub struct GraphicsPipelineDesc<'a, B: Backend> {
    /// A set of graphics shaders to use for the pipeline.
    pub shaders: GraphicsShaderSet<'a, B>,
    /// Rasterizer setup
    pub rasterizer: Rasterizer,
    /// Vertex buffers (IA)
    pub vertex_buffers: Vec<VertexBufferDesc>,
    /// Vertex attributes (IA)
    pub attributes: Vec<AttributeDesc>,
    /// Input assembler attributes, describes how
    /// vertices are assembled into primitives (such as triangles).
    pub input_assembler: InputAssemblerDesc,
    /// Description of how blend operations should be performed.
    pub blender: BlendDesc,
    /// Depth stencil (DSV)
    pub depth_stencil: DepthStencilDesc,
    /// Multisampling.
    pub multisampling: Option<Multisampling>,
    /// Static pipeline states.
    pub baked_states: BakedStates,
    /// Pipeline layout.
    pub layout: &'a B::PipelineLayout,
    /// Subpass in which the pipeline can be executed.
    pub subpass: pass::Subpass<'a, B>,
    /// Options that may be set to alter pipeline properties.
    pub flags: PipelineCreationFlags,
    /// The parent pipeline, which may be
    /// `BasePipeline::None`.
    pub parent: BasePipeline<'a, B::GraphicsPipeline>,
}
複製代碼
相關文章
相關標籤/搜索