Vulkan Shader 的资源绑定与OpenGL的对比。
OpenGL的资源绑定系统
在OpenGL中,资源绑定相对简单但不够灵活:
Uniform变量示例:
uniform mat4 modelMatrix; uniform vec4 color;
|
对应的C++代码:
GLint modelMatrixLoc = glGetUniformLocation(program, "modelMatrix"); GLint colorLoc = glGetUniformLocation(program, "color");
glUseProgram(program); glUniformMatrix4fv(modelMatrixLoc, 1, GL_FALSE, &modelMatrix[0][0]); glUniform4fv(colorLoc, 1, &color[0]);
|
纹理绑定示例:
glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, textureId); glUniform1i(textureLoc, 0);
|
Vulkan的资源绑定系统
Vulkan使用更复杂但更灵活的描述符系统,我们来详细分析每种数据传递方式:
Push Constants(推送常量)
这是最直接和最快的数据传递方式,适合频繁更新的小数据。
着色器代码:
layout(push_constant) uniform PushConstants { mat4 transform; vec4 color; } pushConstants;
|
C++实现:
struct PushConstants { glm::mat4 transform; glm::vec4 color; };
VkPushConstantRange pushConstantRange = {}; pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; pushConstantRange.offset = 0; pushConstantRange.size = sizeof(PushConstants);
PushConstants constants = { ... }; vkCmdPushConstants( commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstants), &constants );
|
特点:
- 数据大小通常限制在128-256字节
- 无需描述符分配
- 更新速度最快
- 适合每次绘制调用都需要更改的小数据
适合较大的、相对稳定的数据。
着色器代码:
layout(std140, set = 0, binding = 0) uniform UniformBufferObject { mat4 model; mat4 view; mat4 proj; } ubo;
|
C++实现:
struct UniformBufferObject { glm::mat4 model; glm::mat4 view; glm::mat4 proj; };
VkBuffer uniformBuffer; VkDeviceMemory uniformBufferMemory; createBuffer( sizeof(UniformBufferObject), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffer, uniformBufferMemory );
VkDescriptorSetLayoutBinding uboLayoutBinding = {}; uboLayoutBinding.binding = 0; uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; uboLayoutBinding.descriptorCount = 1; uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
VkDescriptorBufferInfo bufferInfo = {}; bufferInfo.buffer = uniformBuffer; bufferInfo.offset = 0; bufferInfo.range = sizeof(UniformBufferObject);
VkWriteDescriptorSet descriptorWrite = {}; descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; descriptorWrite.dstSet = descriptorSet; descriptorWrite.dstBinding = 0; descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; descriptorWrite.descriptorCount = 1; descriptorWrite.pBufferInfo = &bufferInfo;
vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);
UniformBufferObject ubo = {}; void* data; vkMapMemory(device, uniformBufferMemory, 0, sizeof(ubo), 0, &data); memcpy(data, &ubo, sizeof(ubo)); vkUnmapMemory(device, uniformBufferMemory);
|
特点:
- 支持较大数据块
- 需要显式管理内存
- 通过描述符集系统访问
- 可以在多个着色器间共享
- 更新频率适中
Storage Buffers(存储缓冲区)
适合需要读写访问的大量数据。
着色器代码:
layout(std430, set = 0, binding = 1) buffer StorageBuffer { vec4 positions[]; } ssbo;
|
C++实现:
VkBuffer storageBuffer; VkDeviceMemory storageBufferMemory; createBuffer( bufferSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, storageBuffer, storageBufferMemory );
|
特点:
- 支持着色器读写访问
- 可以存储动态大小的数据
- 适合计算着色器的数据处理
Combined Image Samplers(组合图像采样器)
用于纹理采样。
着色器代码:
layout(set = 1, binding = 0) uniform sampler2D texSampler;
|
C++实现:
VkImage textureImage; VkImageView textureImageView; VkSampler textureSampler;
VkDescriptorSetLayoutBinding samplerLayoutBinding = {}; samplerLayoutBinding.binding = 0; samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; samplerLayoutBinding.descriptorCount = 1; samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
VkDescriptorImageInfo imageInfo = {}; imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; imageInfo.imageView = textureImageView; imageInfo.sampler = textureSampler;
VkWriteDescriptorSet descriptorWrite = {}; descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; descriptorWrite.dstSet = descriptorSet; descriptorWrite.dstBinding = 0; descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; descriptorWrite.descriptorCount = 1; descriptorWrite.pImageInfo = &imageInfo;
vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);
|
数据流转的关键区别
- 描述符集的层次结构:
- Vulkan使用set和binding两级结构
- 可以在多个管线间共享描述符集
- 支持描述符集的部分更新
- 内存管理:
- Vulkan需要显式管理内存分配和绑定
- 支持多种内存类型和内存属性
- 可以更精确地控制数据传输路径
- 同步机制:
- Vulkan需要显式管理资源访问同步
- 使用内存屏障和事件等同步原语
- 可以更精确地控制数据访问时序
- 批量更新:
- Vulkan支持批量更新描述符集
- 可以一次性更新多个资源绑定
- 减少API调用开销
性能考虑
- Push Constants:
- 最快的数据传输方式
- 直接嵌入命令缓冲区
- 适合高频更新的小数据
- Uniform Buffers:
- 中等访问速度
- 适合较大但更新频率不高的数据
- 可以使用动态偏移优化
- Storage Buffers:
- 描述符集缓存:
- 可以预先分配描述符集池
- 重用描述符集减少开销
- 支持描述符集更新模板