以OpenGL/ES视角介绍gfx-hal(Vulkan) Shader/Program接口使用

news/2024/7/5 6:19:21

文档列表见:Rust 移动端跨平台复杂图形渲染项目开发系列总结(目录)

背景:

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:

  • 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.

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

Chapter 25 Descriptor layout and buffer

Vulkan Tutorial

接下来的故事围绕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

初始化流程如下:

  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>,
}
复制代码

http://www.niftyadmin.cn/n/3752794.html

相关文章

mysql analyze_mysql的analyze,check,checksum,optimize,explain

ANALYZE TABLE用来分析和存储表的关键字的分布&#xff0c;使得系统获得准确的统计信息&#xff0c;影响 SQL 的执行计划的生成。对于数据基本没有发生变化的表&#xff0c;是不需要经常进行表分析的。但是如果表的数据量变化很明显&#xff0c;用户感觉实际的执行计划和预期的…

go 链接mysql当地时间_Golang, MySQL连接不设置时区的问题

Golang, MySQL连接不设置时区的问题发布时间&#xff1a;2020-06-25 08:11:08来源&#xff1a;51CTO阅读&#xff1a;1319作者&#xff1a;EDELWEISS_21gpackage mainimport ("fmt""github.com/go-xorm/xorm"_ "github.com/jinzhu/gorm/dialects/mysq…

无向图最短路径问题

题目&#xff1a;无向图G有N个结点(1<N<1000)及一些边&#xff0c;每一条边上带有正的权重值。 找到结点1到结点N的最短路径&#xff0c;或者输出不存在这样的路径。 解决思路&#xff1a;动态规划 1、首先使用邻接矩阵存储无向图 2、将找到结点1到节点N的最短路径分解成…

java mysql dump_Java 调用Mysql dump 备份数据库详解

SimpleDateFormat sdf new SimpleDateFormat("yyyyMMddHHmmss");try {String name sdf.format(new Date());String filePath System.getProperty("user.dir") "//" name ".sql";// 系统执行器Runtime rt Runtime.getRuntime();…

mysql 商品数据库设计_互联网产品mysql数据库设计总结

mysql数据库性能不比oracle数据库&#xff0c;所以设计上&#xff0c;和oracle有一些不同。下面总结一些互联网产品的数据库设计。1.主键主键可以使用bigint(20) unsigned也可以使用varchar&#xff0c;使用bigint&#xff0c;可以设置为自增主键auto_increment。使用varchar&a…

java多方式登陆_Java 爬虫遇到需要登录的网站,该怎么办?

这是 Java 网络爬虫系列博文的第二篇&#xff0c;在上一篇Java 网络爬虫&#xff0c;就是这么的简单中&#xff0c;我们简单的学习了一下如何利用 Java 进行网络爬虫。在这一篇中我们将简单的聊一聊在网络爬虫时&#xff0c;遇到需要登录的网站&#xff0c;我们该怎么办&#x…

【LeetCode】233. 数字 1 的个数(同剑指Offer43)

一、题目 给定一个整数 n&#xff0c;计算所有小于等于 n 的非负整数中数字 1 出现的个数。 示例: 输入: 13 输出: 6 解释: 数字 1 出现在以下数字中: 1, 10, 11, 12, 13 。二、解决 1、暴力破解 思路&#xff1a; 看完题目&#xff0c;然后可直接写出代码&#xff0c;b…

向文件in.txt中写入字符串helloworld_Python操作文件

1、文件的操作(打开&#xff0c;读&#xff0c;写&#xff0c;重命名&#xff0c;删除等)1.1、打开文件(open)open(文件名,r) : 只读方式打开文件open(文件名,r) : 以读写方式打开&#xff0c;文件不存在报错open(文件名,w) : 可写方式打开文件open(文件名,w) : 以读写方式打开…