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

2023-11-18

文档列表见: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>,
}
复制代码
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • vscode c++解决包含头文件红色波浪线问题

    安装c c 插件后 按ctrl shift p 点击打开了c cpp properties json文件 对其中的IncludePath进行编辑 示例如下 includePath workspaceFolder workspaceFolde
  • CAP理论

    CAP理论在互联网界有着广泛的知名度 知识稍微宽泛一点的工程师都会把其作为衡量系统设计的准则 大家都非常清楚地理解了CAP 任何分布式系统在可用性 一致性 分区容错性方面 不能兼得 最多只能得其二 因此 任何分布式系统的设计只是在三者中的不
  • 靶机练习 No.23 Vulnhub靶机DarkHole 2 .git信息泄露 .bash_history历史命令提权

    靶机练习 No 23 Vulnhub靶机DarkHole 2 0x00 环境准备 0x01 信息收集 步骤一 ip 探测 步骤二 端口扫描 0x02 漏洞挖掘 思路一 web漏洞挖掘 git信息收集 思路二 22ssh 爆破 步骤一 git
  • CentOS7 运维 - 系统基础优化大全

    CentOS7 运维 系统基础优化大全 一 更换Yum源 国内源地址 备份CentOS官方源 使用阿里yum源 使用本地yum源 使用清华yum源 推荐 二 防火墙 临时关闭防火墙 永久关闭防火墙 关闭SELinux 临时关闭 永久关闭 三
  • Linux下SVN操作常用命令

    1 检出 svn co http 路径 目录或文件的全路径 本地目录全路径 username 用户名 password 密码 svn co svn 路径 目录或文件的全路径 本地目录全路径 username 用户名 password 密码
  • FLUKE DSX-5000 CH线缆测试仪怎么升级到新国标

    FLUKE DSX 5000 CH线缆测试仪怎么升级新标准GB T50312 2016 下面山东朗坤小编带给您FLUKE DSX 5000 CH线缆测试仪升级带有新标准软件的整体步骤 一起来学习吧 升级到5 1之后必须使用Linkware9
  • 全网最全总结,有源码,期望、有效值、方差、相关系数、自相关函、互相关函数,还分不清吗

    原文来自公众号 工程师看海 1 期望 期望也就是平均值 是一个数值 反应的是随机变量平均取值的情况 期望也叫做加权平均 在信号中代表直流分量 当随机变量X满足均匀分布时 对一段长度为N的离散序列X x n n 0 1 2 N 1 其期望E
  • 深度学习-1.1卷积

    卷积是一种数学运算 教科书上一般定义 首先有两个函数函数 f g 然后这两个函数的卷积 f g 如下 连续形式 离散形式 1 先将函数g t 延y轴翻转为g t 这是 卷 的由来 2 其中n是卷积的参数 将g t 平移n个长度 变为g n
  • Lua调用C的动态库步骤及接口分析

    Lua调用C的动态库 C语言可以完成一些lua不好实现的功能 当程序主体使用lua完成时 便需要掌握该技巧调用C来帮助我们达到目的 通过调用C的动态库简化操作流程 大致流程如下 使用C语言编写方法提供给lua调用 将C文件打包成动态库 lu
  • excel中怎样将数据合并到一个单元格用逗号隔开

    打开需要操作的Excel表格 将数据合并在一个单元格 并用逗号隔开 如图所示 excel中怎样将数据合并到一个单元格用逗号隔开 选中B1单元格 并输入分隔符号逗号 excel中怎样将数据合并到一个单元格用逗号隔开 单击B1单元格 向下拖动B
  • 使用 ChatGPT 辅助学习——为自己找一个老师

    我们每个人都有许多标签 例如高中生 成绩中等 文科 根据这些标签我和其他拥有相同标签的人分配了相同的教程 班级和老师 这可以带来效率上的提升 因为同一份教程 老师就可以服务几十上百人 而无须为每个人定制 但也正是这样造成了忽略个性的问题 而
  • STM32F103野火之中断

    顾名思义STM32中中断分为内核中断和外部中断 内核中断称为 异常 外部中断称为 中断 IPR寄存器STM只使用了高四位低四位没有使用所以共有2 4种情况 EXTI的功能框图分为两个部分 红色轨迹 模式为中断控制 有中断控制寄存器位决定 绿
  • Hyperledger Fabric 示例启动流程(二)

    test network启动流程 1 启动测试网络 此命令创建一个由两个对等节点和一个排序节点组成的Fabric网络 network sh up createOrgs 使用cryptogen工具或者Fabric CA来创建Org1 Org2
  • 嵌入式linux如何下载程序,Linux平台的下载程序-嵌入式系统-与非网

    有许多网络文件下载工具可以在Windows平台下很好地工作 如NetAnts 网际快车 TelePro等 还有像WebZip那样功能强大的离线浏览器 这些工具使我们可以在Windows环境下很轻松地下载网站上的文件 目录 网站的一部分 甚至
  • 大数据抗疫的“洪荒之力”:多地政府借力大数据技术,多家企业上马大数据产品...

    来源 数据猿 ID datayuancn 当心 几十万 3M口罩 全是假货 可能会携带病毒细菌 世上有两样东西不能直视 一是太阳 二是人心 生死之间 利益之前 最不可低估的就是人性的恶 目录精要 武汉火神山医院利用天翼云大数据检测疫情 潍坊
  • vscode运行cpp文件:检测到 #include 错误。请更新 includePath。已为此翻译单元(E:\C++ Code\test1\test1\test1.cpp)禁用波形曲线。

    刚为vscode配置好C 编译环境准备刷leetcode 结果写cpp文件时发现 include头文件总是报错 我就很迷惑了 include
  • Debian10常用命令

    文章目录 1 常用文件路径 2 用户和组管理 3 网络配置 4 主机名修改 5 vim编辑器 6 文件的操作 7 关机命令 1 常用文件路径 网卡配置文件 etc network interfaces dns配置文件 etc resolv
  • IP多播概念和实现

    目录 基本概念 硬件多播 IGMP和多播路由选择协议 基本概念 IP多播 曾译为组播 的目的是更好地支持一对多通信 即一个源点发送到多个终点 例如实时信息的交付 新闻 股市行情 软件更新 交互式会议及其他多媒体通信 下图中的多播组有90个成
  • SQL基础 读书笔记 第一章

    SQL基础教程 笔记 第一章 表格的新建 修改和删除 1 DDL DML DCL DDL 数据定义语言 CREATE DROP ALTER ALTER 修改数据库和表等对象的结构 比如增加 修改约束条件 比如 修改字段属性 INT 改为CH
  • 以OpenGL/ES视角介绍gfx-hal(Vulkan) Shader/Program接口使用

    文档列表见 Rust 移动端跨平台复杂图形渲染项目开发系列总结 目录 背景 The right way to tackle this in Vulkan is to use resource descriptors A descriptor