Vulkan Tutorial 5 顶点缓冲区

news2024/12/29 18:41:51

目录

16 顶点缓冲区

顶点着色器

顶点数据

管道顶点输入

17  顶点缓冲区创建

缓冲区创建

内存要求

内存分配

填充顶点缓冲区

 18  暂存缓冲区

传输队列

使用暂存缓冲区

19 索引缓冲区

索引缓冲区创建

使用索引缓冲区


16 顶点缓冲区

我们将用内存中的顶点缓冲区替换顶点着色器中的硬编码顶点数据。我们将从最简单的方法开始,即创建 CPU 可见缓冲区并使用memcpy直接将顶点数据复制到其中。

顶点着色器

首先更改顶点着色器,使其不再在着色器代码本身中包含顶点数据。顶点着色器使用关键字从顶点缓冲区获取输入 in

#version 450

//layout(location = x)注释将索引分配给我们稍后可以用来引用它们的输入
layout(location = 0) in vec2 inPosition;
//inPosition变量和inColor是顶点属性
layout(location = 1) in vec3 inColor;

layout(location = 0) out vec3 fragColor;

void main() {
    gl_Position = vec4(inPosition, 0.0, 1.0);
    fragColor = inColor;
}

顶点数据

我们将顶点数据从着色器代码移动到程序代码中的数组。首先包括 GLM 库,它为我们提供线性代数相关类型,如向量和矩阵。我们将使用这些类型来指定位置和颜色向量。

#include <glm/glm.hpp>
struct Vertex {
    glm::vec2 pos;
    glm::vec3 color;

//顶点绑定描述了在整个顶点中从内存加载数据的速率

    static VkVertexInputBindingDescription getBindingDescription() {
        VkVertexInputBindingDescription bindingDescription{};
//我们所有的逐顶点数据都打包在一个数组中,因此我们只需要一个绑定

bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
        return bindingDescription;
    }

//另一个辅助函数来Vertex填充这些结构
static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescriptions() {
        std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};
//属性描述结构描述了如何从源自绑定描述的顶点数据块中提取顶点属性。
//我们有两个属性,位置和颜色,所以我们需要两个属性描述结构。
        attributeDescriptions[0].binding = 0;
        attributeDescriptions[0].location = 0;
        attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
        attributeDescriptions[0].offset = offsetof(Vertex, pos);

        attributeDescriptions[1].binding = 0;
        attributeDescriptions[1].location = 1;
//该format参数描述属性的数据类型
        attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
        attributeDescriptions[1].offset = offsetof(Vertex, color);

        return attributeDescriptions;
    }


};

//GLM 方便地为我们提供了与着色器语言中使用的矢量类型完全匹配的 C++ 类型。

const std::vector<Vertex> vertices = {
    {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}},
    {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},
    {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}
};

stride参数指定从一个条目到下一个条目的字节数,该inputRate参数可以具有以下值之一:

  • VK_VERTEX_INPUT_RATE_VERTEX:移动到每个顶点后的下一个数据条目
  • VK_VERTEX_INPUT_RATE_INSTANCE:移动到每个实例后的下一个数据条目

管道顶点输入

我们现在需要设置图形管道以通过引用createGraphicsPipeline. 找到 vertexInputInfo结构并修改它以引用两个描述:

auto bindingDescription = Vertex::getBindingDescription();
auto attributeDescriptions = Vertex::getAttributeDescriptions();

vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();

17  顶点缓冲区创建

Vulkan 中的缓冲区是用于存储可由显卡读取的任意数据的内存区域。它们可以用来存储顶点数据.与我们目前处理的 Vulkan 对象不同,缓冲区不会自动为自己分配内存。

缓冲区创建

创建一个新函数createVertexBuffer调用它。

void createVertexBuffer() {
//创建缓冲区需要我们填充一个VkBufferCreateInfo结构。

VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
//以字节为单位指定缓冲区的大小
bufferInfo.size = sizeof(vertices[0]) * vertices.size();
//指示将要使用缓冲区中的数据的目的
bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
 if (vkCreateBuffer(device, &bufferInfo, nullptr, &vertexBuffer) != VK_SUCCESS) {
        throw std::runtime_error("failed to create vertex buffer!");
    }

}

我们现在可以创建缓冲区了vkCreateBuffer。定义一个类成员来保存缓冲区句柄并调用它vertexBuffer

VkBuffer vertexBuffer;

vkDestroyBuffer(device, vertexBuffer, nullptr);

内存要求

缓冲区已创建,但实际上尚未分配任何内存。为缓冲区分配内存的第一步是使用适当命名的

VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements);
//缓冲区已创建,但实际上尚未分配任何内存。
//为缓冲区分配内存的第一步是使用适当命名的vkGetBufferMemoryRequirements 函数查询其内存需求。

该VkMemoryRequirements结构具有三个字段:

  • size:所需内存量的大小(以字节为单位)可能与 不同 bufferInfo.size
  • alignment: 缓冲区在分配的内存区域中开始的字节偏移量,取决于bufferInfo.usagebufferInfo.flags
  • memoryTypeBits:适合缓冲区的内存类型的位字段。

显卡可以提供不同类型的内存供分配。每种类型的内存在允许的操作和性能特征方面各不相同。我们需要结合缓冲区的要求和我们自己的应用程序要求来找到合适的内存类型来使用。让我们findMemoryType为此创建一个新函数。

uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {

    VkPhysicalDeviceMemoryProperties memProperties;
    vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
//结构有两个数组memoryTypes 和memoryHeaps。
//内存堆是不同的内存资源,例如专用 VRAM 和 RAM 中用于 VRAM 耗尽时的交换空间。
//这些堆中存在不同类型的内存。
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
    if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
//该typeFilter参数将用于指定适合的内存类型的位域
        return i;
    }
}

throw std::runtime_error("failed to find suitable memory type!");
    
}

内存分配

我们现在有办法确定正确的内存类型,所以我们可以通过填充结构来实际分配内存VkMemoryAllocateInfo。

VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);

内存分配现在就像指定大小和类型一样简单,这两者都源自顶点缓冲区的内存要求和所需的属性。创建一个类成员将句柄存储到内存中,并使用vkAllocateMemory.

VkDeviceMemory vertexBufferMemory;

如果内存分配成功,那么我们现在可以使用以下方法将此内存与缓冲区相关联vkBindBufferMemory:前三个参数是不言自明的,第四个参数是内存区域内的偏移量。由于此内存是专门为此顶点缓冲区分配的,因此偏移量很简单0。如果偏移量不为零,则它需要被 整除memRequirements.alignment

vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);

填充顶点缓冲区

现在是将顶点数据复制到缓冲区的时候了。

void* data;
vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);
//允许我们访问由偏移量和大小定义的指定内存资源区域。这里的偏移量和大小分别是0和 bufferInfo.size
//倒数第二个参数可用于指定标志,但当前 API 中尚无可用标志。它必须设置为值0。
//最后一个参数指定指向映射内存的指针的输出。

 memcpy(data, vertices.data(), (size_t) bufferInfo.size);
//v\简单地memcpy将顶点数据存储到映射的内存中,然后使用vkUnmapMemor
vkUnmapMemory(device, vertexBufferMemory);

//在渲染操作期间绑定顶点缓冲区
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);

VkBuffer vertexBuffers[] = {vertexBuffer};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
//除了命令缓冲区,还指定偏移量和我们要为其指定顶点缓冲区的绑定数

vkCmdDraw(commandBuffer, static_cast<uint32_t>(vertices.size()), 1, 0, 0);
//调用更改为vkCmdDraw传递缓冲区中的顶点数,而不是硬编码的数字3。

 18  暂存缓冲区

我们现在拥有的顶点缓冲区可以正常工作,但允许我们从 CPU 访问它的内存类型可能不是显卡本身读取的最佳内存类型。最佳内存有 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT标志,通常不能被专用显卡上的 CPU 访问。

传输队列

缓冲区复制命令需要一个支持传输操作的队列系列,使用 表示VK_QUEUE_TRANSFER_BIT任何具有VK_QUEUE_GRAPHICS_BITVK_QUEUE_COMPUTE_BIT 功能的队列系列都已经隐含地支持VK_QUEUE_TRANSFER_BIT操作。我们将创建两个顶点缓冲区。 CPU 可访问内存中的一个暂存缓冲区,用于将数据从顶点数组上传到,最后一个顶点缓冲区位于设备本地内存中。然后,我们将使用缓冲区复制命令将数据从暂存缓冲区移动到实际的顶点缓冲区。

  • 修改QueueFamilyIndicesfindQueueFamilies显式查找带有 bit 的队列系列VK_QUEUE_TRANSFER_BIT,而不是 VK_QUEUE_GRAPHICS_BIT.
  • 修改createLogicalDevice以请求传输队列的句柄
  • 为在传输队列系列上提交的命令缓冲区创建第二个命令池
  • 将资源更改sharingModeVK_SHARING_MODE_CONCURRENT并指定图形和传输队列系列
  • 将任何传输命令vkCmdCopyBuffer(我们将在本章中使用)提交到传输队列而不是图形队列

因为我们将在本章中创建多个缓冲区,所以将缓冲区创建移至辅助函数是一个好主意。创建一个新函数 createBuffer 并将 createVertexBuffer 中的代码(映射除外)移动到它。

void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) {
    VkBufferCreateInfo bufferInfo{};
    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.size = size;
    bufferInfo.usage = usage;
    bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

    if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) {
        throw std::runtime_error("failed to create buffer!");
    }

    VkMemoryRequirements memRequirements;
    vkGetBufferMemoryRequirements(device, buffer, &memRequirements);

    VkMemoryAllocateInfo allocInfo{};
    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.allocationSize = memRequirements.size;
    allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);

    if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) {
        throw std::runtime_error("failed to allocate buffer memory!");
    }

    vkBindBufferMemory(device, buffer, bufferMemory, 0);

确保为缓冲区大小、内存属性和用法添加参数,以便我们可以使用此函数创建许多不同类型的缓冲区。最后两个参数是要写入句柄的输出变量。

使用暂存缓冲区

我们现在要更改createVertexBuffer为仅使用主机可见缓冲区作为临时缓冲区,并使用设备本地缓冲区作为实际顶点缓冲区。

void createVertexBuffer() {
    VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size();

//使用新的stagingBuffer、stagingBufferMemory来映射和复制顶点数据
    VkBuffer stagingBuffer;
    VkDeviceMemory stagingBufferMemory;
    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);

    void* data;
    vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data);
        memcpy(data, vertices.data(), (size_t) bufferSize);
    vkUnmapMemory(device, stagingBufferMemory);

    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory);
}
  • VK_BUFFER_USAGE_TRANSFER_SRC_BIT:缓冲区可以用作内存传输操作中的源。
  • VK_BUFFER_USAGE_TRANSFER_DST_BIT:缓冲区可用作内存传输操作中的目标。

vertexBuffer从设备本地的内存类型分配的,这通常意味着我们无法使用vkMapMemory. 但是,我们可以将数据从 stagingBuffer复制到vertexBuffer。必须通过指定 stagingBuffer的传输源标志和vertexBuffer 的传输目标标志以及顶点缓冲区使用标志来表明 。

我们现在要编写一个函数,将内容从一个缓冲区复制到另一个缓冲区,称为copyBuffer.

void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
    VkCommandBufferAllocateInfo allocInfo{};
//内存传输操作是使用命令缓冲区执行的,就像绘图命令一样。因此我们必须首先分配一个临时命令缓冲区。
    allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    allocInfo.commandPool = commandPool;//短期缓冲区创建一个单独的命令池
    allocInfo.commandBufferCount = 1;
//
    VkCommandBuffer commandBuffer;
    vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
//立即开始记录命令缓冲区
    VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

vkBeginCommandBuffer(commandBuffer, &beginInfo);
//只打算使用命令缓冲区一次,然后等待从函数返回,直到复制操作完成执行
    VkBufferCopy copyRegion{};
copyRegion.srcOffset = 0; // Optional
copyRegion.dstOffset = 0; // Optional
copyRegion.size = size;
//使用命令传输缓冲区的内容vkCmdCopyBuffer。它以源缓冲区和目标缓冲区作为参数,以及要复制的区域数组。
vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);

    vkEndCommandBuffer(commandBuffer);
//命令缓冲区仅包含复制命令,因此我们可以立即停止记录。现在执行命令缓冲区以完成传输:

VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
//与绘制命令不同,这次我们不需要等待任何事件。我们只想立即在缓冲区上执行传输
vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(graphicsQueue);

vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);


}

我们现在可以调用copyBuffer函数createVertexBuffer将顶点数据移动到设备本地缓冲区,将数据从暂存缓冲区复制到设备缓冲区后,我们应该清理它:

copyBuffer(stagingBuffer, vertexBuffer, bufferSize);
        vkDestroyBuffer(device, stagingBuffer, nullptr);
        vkFreeMemory(device, stagingBufferMemory, nullptr);

19 索引缓冲区

在真实世界应用程序中渲染的 3D 网格通常会在多个三角形之间共享顶点。

绘制一个矩形需要两个三角形,这意味着我们需要一个有 6 个顶点的顶点缓冲区。问题是需要复制两个顶点的数据,导致 50% 的冗余。对于更复杂的网格,情况只会变得更糟,其中顶点在平均 3 个三角形中重复使用。这个问题的解决方案是使用索引缓冲区

索引缓冲区本质上是指向顶点缓冲区的指针数组。它允许您重新排序顶点数据,并为多个顶点重用现有数据。上图演示了如果我们有一个包含四个唯一顶点中的每一个的顶点缓冲区,则索引缓冲区对于矩形来说会是什么样子。前三个索引定义右上三角形,后三个索引定义左下三角形的顶点。

索引缓冲区创建

修改顶点数据并添加索引数据以绘制图中所示的矩形。修改顶点数据以表示四个角:左上角是红色,右上角是绿色,右下角是蓝色,左下角是白色。

const std::vector<Vertex> vertices = {
    {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}},
    {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}},
    {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}},
    {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}
};

 添加一个新数组indices来表示索引缓冲区的内容。

const std::vector<uint16_t> indices = {
    0, 1, 2, 2, 3, 0
};

//定义两个新的类成员来保存索引缓冲区的资源:

VkBuffer vertexBuffer;
VkDeviceMemory vertexBufferMemory;
VkBuffer indexBuffer;
VkDeviceMemory indexBufferMemory;

void createIndexBuffer() {
    VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size();//索引数bufferSize乘以索引类型的大小

    VkBuffer stagingBuffer;
    VkDeviceMemory stagingBufferMemory;
//
    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);

    void* data;
    vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data);
    memcpy(data, indices.data(), (size_t) bufferSize);
    vkUnmapMemory(device, stagingBufferMemory);
//VK_BUFFER_USAGE_INDEX_BUFFER_BIT而不是 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT

    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory);

    copyBuffer(stagingBuffer, indexBuffer, bufferSize);

    vkDestroyBuffer(device, stagingBuffer, nullptr);
    vkFreeMemory(device, stagingBufferMemory, nullptr);
}

使用索引缓冲区

使用索引缓冲区进行绘图涉及对 recordCommandBuffer. 我们首先需要绑定索引缓冲区,就像我们为顶点缓冲区所做的那样。不同之处在于您只能有一个索引缓冲区。不幸的是,不可能为每个顶点属性使用不同的索引,因此即使只有一个属性发生变化,我们仍然必须完全复制顶点数据。

vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16);

仅绑定索引缓冲区还不会改变任何内容,我们还需要更改绘图命令以告知 Vulkan 使用索引缓冲区。删除该 vkCmdDraw行并将其替换为vkCmdDrawIndexed:

vkCmdDrawIndexed(commandBuffer, static_cast<uint32_t>(indices.size()), 1, 0, 0, 0);

对此函数的调用与vkCmdDraw. 前两个参数指定索引数和实例数。我们没有使用实例化,所以只需指定1实例。索引数表示将传递给顶点着色器的顶点数。下一个参数指定索引缓冲区的偏移量,使用值1将导致图形卡在第二个索引处开始读取。倒数第二个参数指定要添加到索引缓冲区中的索引的偏移量。最后一个参数指定实例化的偏移量,我们没有使用它。

现在运行你的程序,你应该看到以下内容:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/582954.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue-admin-template框架搭建及应用

一、框架介绍 vue-admin-template是基于vue-element-admin的一套后台管理系统基础模板&#xff08;最少精简版&#xff09;&#xff0c;可作为模板进行二次开发&#xff1b; 可以把 vue-element-admin当做工具箱或者集成方案仓库&#xff0c;在 vue-admin-template 的基础上进…

FPGA基于AXI 1G/2.5G Ethernet Subsystem实现千兆UDP通信 提供工程源码和技术支持

目录 1、前言2、我这里已有的UDP方案3、详细设计方案传统UDP网络通信方案本方案详细设计说明UDP层设计AXIS-FIFOAXI 1G/2.5G Ethernet Subsystem&#xff1a;输出 4、vivado工程详解5、上板调试验证并演示系统配置UDP数据回环测试注意事项 6、福利&#xff1a;工程代码的获取 1…

SpringMVC第七阶段:SpringMVC的增删改查(01)

SpringMVC的增删改查 1、准备单表的数据库 drop database if exists springmvc;create database springmvc;use springmvc; ##创建图书表 create table t_book(id int(11) primary key auto_increment, ## 主键name varchar(50) not null, ## 书名 author varchar(50) no…

敏捷缺陷「bug」跟踪管理

一般情况下&#xff0c;当前迭代的缺陷&#xff0c;建议放到本迭代的迭代看板上&#xff0c;在迭代结束前修复完成。 “缺陷看板”通常存放发布后遗留的缺陷&#xff0c;客户反馈的缺陷&#xff0c;生产环境发现的缺陷等。 在Leangoo领歌的敏捷项目中&#xff0c;默认创建了“…

安科瑞能源管理系统基于物联网技术应用

安科瑞 徐浩竣 江苏安科瑞电器制造有限公司 zx acrelxhj 摘 要:在能源形势紧张的大趋势下,高能耗的大型公共建筑能源管理系统的建设逐渐受到重视,以物联网技术及基础的建筑能源管理平台可以提供即时、准确、高效的能源管理策略。 系统阐述了结合物联网技术的建筑能源管理构建…

关于自动映射在项目中的具体落地(dozer)

关于自动映射在项目中的具体落地&#xff08;dozer&#xff09; 项目开发过程中&#xff0c;经常需要编写model之间的转换&#xff0c;最常见的有&#xff1a; 实体转DTODTO转实体 等操作,故为了简化代码的开发工作&#xff0c;需要简化对象属性之间复制的步骤&#xff0c;目…

分类、标签设计及查询优化

文章目录 问题分类和标签的设计知名开源系统的设计jive论坛Solo博客系统的设计wordpress的数据库设计 参考链接 问题 在很多业务系统中&#xff0c;都有对对象的分类和标签设计。在数据库层面如何设计相应的表&#xff0c;以及如何做查询优化&#xff0c;是一个比较普遍的问题…

MKS SERVO4257D 闭环步进电机_系列5 CAN指令说明

第1部分 产品介绍 MKS SERVO 28D/35D/42D/57D 系列闭环步进电机是创客基地为满足市场需求而自主研发的一款产品。具备脉冲接口和RS485/CAN串行接口&#xff0c;支持MODBUS-RTU通讯协议&#xff0c;内置高效FOC矢量算法&#xff0c;采用高精度编码器&#xff0c;通过位置反馈&am…

客户案例 | 思腾合力GPU算力节点助力实时云渲染

客户介绍 平行云是国内领先的云化XR概念倡导者与技术先行者&#xff0c;LarkXR是平行云研发的云化XR PaaS平台。LarkXR能够帮助XR领域企业级客户快速实现技术、产品及平台的云化转型&#xff0c;高效使能企业的云化XR业务&#xff0c;有效保护客户的内容安全&#xff0c;让多种…

ANDEAWELL:国产工业RFID替代潮即将到来!

受这两年国外的芯片供应不足&#xff0c;价格上涨后用户难以承受等影响&#xff0c;越来越多的企业选择国产替代芯片。随着国产替代芯片的应用增加&#xff0c;东信源芯、旗连、国芯物联等出货量也开始增加&#xff0c;可以预见的是&#xff0c;国产工业RFID替代潮即将到来! 国…

解决Maven 依赖下载不全的问题 (自测有效)

问题描述&#xff1a;从仓库拉代码下来 发现存在部分依赖下载不了。 解决方案&#xff1a;去中央仓库一个个手动下载。具体步骤如下 1.清除下载一般的依赖 &#xff08;以下代码是 .bat 文件&#xff09; echo off rem create by NettQunrem 这里写你的仓库路径 s…

类实例化和实例初始化

就算不写main方法里面的3句&#xff0c;也会执行5 1 10 6 因为main方法所在的类需要先加载和初始化 执行顺序如下&#xff1a;先初始化父类再初始化子类 静态实例变量显示赋值和静态代码块代码从上到下顺序执行&#xff08;根据书写顺序&#xff09; 子类的实例化方法&am…

【9 Vue全家桶 – Vuex状态管理】

1 什么是状态管理 其实是数据管理但是为了更好的指出是由于状态的变化导致数据的变化(响应式数据),我们称之为状态管理. 2 Vuex的状态管理 组件只能直接读取state,而不能直接修改state,必须通过mutation才能修改.(pinia可以直接读取和修改state) 3 Vuex的安装 npm install …

ModDrop++:一种具有受试者内部协同训练的动态滤波网络,用于具有缺失模态的多发性硬化病变分割

文章目录 ModDrop: A Dynamic Filter Network with Intra-subject Co-training for Multiple Sclerosis Lesion Segmentation with Missing Modalities摘要本文方法Dynamic Head with Filter ScalingIntra-subject Co-training 实验结果 ModDrop: A Dynamic Filter Network wit…

1:面向对象

文章目录 1&#xff1a;与equals的区别2&#xff1a;写算法题的时候边界条件最后考虑3&#xff1a;高内聚低耦合4&#xff1a;父类引用指向子类对象5&#xff1a;如何重写equals方法6&#xff1a;java是如果实现跨平台的7&#xff1a;HashMap中的重点注意事项8&#xff1a;局部…

快速入门Python语言:人生苦短,我用Python~~Python语言经验分享

⭐方向一&#xff1a;“你是如何学习/自学 Python 的&#xff1f;” 我通过自学的方式学习Python。我的自学方法具有良好的灵活性和自控力&#xff0c;在这个过程中&#xff0c;我注重打下坚实的基础&#xff0c;确保学习的深度与广度&#xff0c;以提高对Python语言和编程的认…

[答疑]UMLChina的Logo是不是不对劲

DDD领域驱动设计批评文集>> 《软件方法》强化自测题集>> 《软件方法》各章合集>> Lynn 2023-4-6 13:55 潘老师&#xff0c;咱umlchina的标记是不是不太对劲&#xff0c;火柴人指向用例的箭头是三角形似乎不合uml的标准。 UMLChina潘加宇 是的。如果按U…

张驰咨询:六西格玛绿带培训如何帮助酒店降低成本和提高客户满意度?

六西格玛是一种质量管理方法&#xff0c;旨在通过减少缺陷和提高效率来提高客户满意度。在酒店行业中&#xff0c;六西格玛可以帮助酒店降低成本和提高客户满意度的方法有&#xff1a;减少浪费、提高效率、优化客户体验、降低维护成本等等。下面张驰咨询给大家分享两个真实案例…

免改造数据安全技术,实现企业关键数据资产保护落地

4月26日&#xff0c;腾讯安全联合中国信通院“数据安全推进计划”共同在深圳举办了数据安全研讨会。炼石网络创始人兼CEO白小勇受邀出席&#xff0c;分享了“免改造数据安全的实践与思考”的议题&#xff0c;与中国信息通信研究院云计算与大数据研究所高级业务主管龚诗然、腾讯…

SpringMvc源码分析

概述 用户的请求&#xff0c;是如何被 DispatcherServlet 处理的 先看图 从图中可以看到请求首先是被 DispatcherServlet 所处理&#xff0c;但是实际上&#xff0c;FrameworkServlet 先被触发 我们看下处理各种请求的方法 Overrideprotected final void doGet(HttpServletR…