GLSL 计算着色器仅部分写入 Vulkan 中的缓冲区
GLSL Compute Shader only Partially writing onto Buffer in Vulkan
我创建了这个 GLSL 计算着色器并使用 "glslangValidator.exe" 编译了它。但是,它只会更新 "Particles[i].Velocity" 值,而不会更新任何其他值,这只会在某些情况下发生。我检查过使用 "RenderDoc".
发送的输入值是否正确
缓冲区使用标志位
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT
和 属性 标志位
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
GLSL 着色器
#version 450
#extension GL_ARB_separate_shader_objects : enable
struct Particle
{
vec3 Position;
vec3 Velocity;
vec3 IPosition;
vec3 IVelocity;
float LifeTime;
float ILifetime;
};
layout(binding = 0) buffer Source
{
Particle Particles[ ];
};
layout(binding = 1) uniform UBO
{
mat4 model;
mat4 view;
mat4 proj;
float time;
};
vec3 Gravity = vec3(0.0f,-0.98f,0.0f);
float dampeningFactor = 0.5;
void main(){
uint i = gl_GlobalInvocationID.x;
if(Particles[i].LifeTime > 0.0f){
Particles[i].Velocity = Particles[i].Velocity + Gravity * dampeningFactor * time;
Particles[i].Position = Particles[i].Position + Particles[i].Velocity * time;
Particles[i].LifeTime = Particles[i].LifeTime - time;
}else{
Particles[i].Velocity = Particles[i].IVelocity;
Particles[i].Position = Particles[i].IPosition;
Particles[i].LifeTime = Particles[i].ILifetime;
}
}
描述符集布局绑定
VkDescriptorSetLayoutBinding descriptorSetLayoutBindings[2] = {
{ 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, 0 },
{ 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, 0 }
};
命令调度
vkCmdDispatch(computeCommandBuffers, MAX_PARTICLES , 1, 1);
提交队列
VkSubmitInfo cSubmitInfo = {};
cSubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
cSubmitInfo.commandBufferCount = 1;
cSubmitInfo.pCommandBuffers = &computeCommandBuffers;
if (vkQueueSubmit(computeQueue.getQueue(), 1, &cSubmitInfo, computeFence) != VK_SUCCESS) {
throw std::runtime_error("failed to submit compute command buffer!");
}
vkWaitForFences(device.getDevice(), 1, &computeFence, VK_TRUE, UINT64_MAX);
更新:13/05/2017(添加了更多信息)
CPP 中的粒子结构定义
struct Particle {
glm::vec3 location;
glm::vec3 velocity;
glm::vec3 initLocation;
glm::vec3 initVelocity;
float lifeTime;
float initLifetime;
}
数据映射到存储缓冲区
void* data;
vkMapMemory(device.getDevice(), stagingBufferMemory, 0, bufferSize, 0, &data);
memcpy(data, particles, (size_t)bufferSize);
vkUnmapMemory(device.getDevice(), stagingBufferMemory);
copyBuffer(stagingBuffer, computeBuffer, bufferSize);
复制缓冲区函数(来自 vulkan 的 Alexander Overvoorde-tutorial.com)
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.getCommandPool();
allocInfo.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
vkAllocateCommandBuffers(device.getDevice(), &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.size = size;
vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region);
vkEndCommandBuffer(commandBuffer);
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
vkQueueSubmit(graphicsQueue.getQueue(), 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(graphicsQueue.getQueue());
vkFreeCommandBuffers(device.getDevice(), commandPool.getCommandPool(), 1, &commandBuffer);
}
看看这个 Whosebug 问题:
最终更正答案:
在你的例子中,你结构的最大成员是 vec3(浮点数的三元素向量)。 vec3 的基本对齐方式与 vec4 的对齐方式相同。所以你的数组元素的基本对齐等于 16 个字节。这意味着 您数组的每个元素都必须从 16.
的倍数的地址开始
但是对齐规则必须递归地应用于每个结构成员。 3 元素向量与 4 元素向量具有相同的对齐方式。这意味着:
Position
成员以与每个数组成员相同的对齐方式开始
Velocity
、IPosition
和 IVelocity
成员必须在给定数组元素开始后的 16 字节的倍数处开始。
LifeTime
和 ILifeTime
成员具有 4 字节对齐。
因此您的结构的总大小(以字节为单位)等于:
Position
- 16 字节(Position
本身占用 12 字节,但下一个成员有 16 字节对齐)
Velocity
- 16 字节
IPosition
- 16 字节
IVelocity
+ LifeTime
- 16 字节
ILifeTime
- 4 个字节
这给出了 68 个字节。因此,据我了解,您需要在结构末尾填充 12 个字节(数组元素之间额外 12 个字节),因为每个数组元素必须从 16 的倍数的地址开始。
因此第一个数组元素从绑定到存储缓冲区的内存的偏移量 0 开始。但是第二个数组元素必须从内存开始的偏移量 80 开始(大于 68 的 16 的最接近倍数)等等。
或者,正如@NicolBolas 评论的那样,为了让生活更轻松,只将所有内容打包到 vec4 成员中 ;-)。
虽然不完全正确但更好:
在你的例子中,你结构的最大成员是 vec3(浮点数的三元素向量)。因此,您的数组元素的基本对齐方式等于 12 个字节(对于 std430 布局中的结构数组,基本对齐方式不必四舍五入为 4 元素向量的 mach 对齐方式。< - 在这里我错了。我们不必对结构的基对齐进行舍入,但其成员的对齐是正常计算的,vec3 对齐与 vec4 对齐相同)。这意味着 Your array 的每个元素都必须从 12 的倍数的地址开始(不,在这种情况下它应该从 16 的倍数开始)。
但是对齐规则必须递归地应用于每个结构成员。 3 元素向量与 4 元素向量具有相同的对齐方式。这意味着:
Position
成员以与每个数组成员相同的对齐方式开始
Velocity
、IPosition
和 IVelocity
成员必须在给定数组元素开始后的 16 字节的倍数处开始。
LifeTime
和 ILifeTime
成员具有 4 字节对齐。
因此您的结构的总大小(以字节为单位)等于:
Position
- 16 字节(Position
本身占用 12 字节,但下一个成员有 16 字节对齐)
Velocity
- 16 字节
IPosition
- 16 字节
IVelocity
+ LifeTime
- 16 字节
ILifeTime
- 4 个字节
这给出了 68 个字节。因此,据我了解,您需要在结构末尾进行 4 字节填充(数组元素之间额外 4 字节),因为每个数组元素必须从 12 的倍数地址开始(同样,我们在这里需要 12 字节的填充,以便下一个数组元素从 16 的倍数开始,而不是 12).
因此第一个数组元素从绑定到存储缓冲区的内存的偏移量 0 开始。但是第二个数组元素必须从内存开始的偏移量 72 开始(大于 68 的 12 的最接近倍数)等等。
前一个错误答案:
在您的例子中,最大的成员是 vec3(浮点数的三元素向量)。它的对齐等于 12 个字节(在结构数组的情况下,我们不必将 3 元素向量的对齐舍入到 4 元素向量的 mach 对齐)。 Your struct 的大小(以字节为单位)等于 56。因此,据我了解,您需要在结构末尾填充 4 个字节(数组元素之间还有 4 个字节),因为每个数组元素必须从以下地址开始是 12 的倍数。
我创建了这个 GLSL 计算着色器并使用 "glslangValidator.exe" 编译了它。但是,它只会更新 "Particles[i].Velocity" 值,而不会更新任何其他值,这只会在某些情况下发生。我检查过使用 "RenderDoc".
发送的输入值是否正确缓冲区使用标志位
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT
和 属性 标志位
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
GLSL 着色器
#version 450
#extension GL_ARB_separate_shader_objects : enable
struct Particle
{
vec3 Position;
vec3 Velocity;
vec3 IPosition;
vec3 IVelocity;
float LifeTime;
float ILifetime;
};
layout(binding = 0) buffer Source
{
Particle Particles[ ];
};
layout(binding = 1) uniform UBO
{
mat4 model;
mat4 view;
mat4 proj;
float time;
};
vec3 Gravity = vec3(0.0f,-0.98f,0.0f);
float dampeningFactor = 0.5;
void main(){
uint i = gl_GlobalInvocationID.x;
if(Particles[i].LifeTime > 0.0f){
Particles[i].Velocity = Particles[i].Velocity + Gravity * dampeningFactor * time;
Particles[i].Position = Particles[i].Position + Particles[i].Velocity * time;
Particles[i].LifeTime = Particles[i].LifeTime - time;
}else{
Particles[i].Velocity = Particles[i].IVelocity;
Particles[i].Position = Particles[i].IPosition;
Particles[i].LifeTime = Particles[i].ILifetime;
}
}
描述符集布局绑定
VkDescriptorSetLayoutBinding descriptorSetLayoutBindings[2] = {
{ 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, 0 },
{ 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, 0 }
};
命令调度
vkCmdDispatch(computeCommandBuffers, MAX_PARTICLES , 1, 1);
提交队列
VkSubmitInfo cSubmitInfo = {};
cSubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
cSubmitInfo.commandBufferCount = 1;
cSubmitInfo.pCommandBuffers = &computeCommandBuffers;
if (vkQueueSubmit(computeQueue.getQueue(), 1, &cSubmitInfo, computeFence) != VK_SUCCESS) {
throw std::runtime_error("failed to submit compute command buffer!");
}
vkWaitForFences(device.getDevice(), 1, &computeFence, VK_TRUE, UINT64_MAX);
更新:13/05/2017(添加了更多信息)
CPP 中的粒子结构定义
struct Particle {
glm::vec3 location;
glm::vec3 velocity;
glm::vec3 initLocation;
glm::vec3 initVelocity;
float lifeTime;
float initLifetime;
}
数据映射到存储缓冲区
void* data;
vkMapMemory(device.getDevice(), stagingBufferMemory, 0, bufferSize, 0, &data);
memcpy(data, particles, (size_t)bufferSize);
vkUnmapMemory(device.getDevice(), stagingBufferMemory);
copyBuffer(stagingBuffer, computeBuffer, bufferSize);
复制缓冲区函数(来自 vulkan 的 Alexander Overvoorde-tutorial.com)
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.getCommandPool();
allocInfo.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
vkAllocateCommandBuffers(device.getDevice(), &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.size = size;
vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region);
vkEndCommandBuffer(commandBuffer);
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
vkQueueSubmit(graphicsQueue.getQueue(), 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(graphicsQueue.getQueue());
vkFreeCommandBuffers(device.getDevice(), commandPool.getCommandPool(), 1, &commandBuffer);
}
看看这个 Whosebug 问题:
最终更正答案:
在你的例子中,你结构的最大成员是 vec3(浮点数的三元素向量)。 vec3 的基本对齐方式与 vec4 的对齐方式相同。所以你的数组元素的基本对齐等于 16 个字节。这意味着 您数组的每个元素都必须从 16.
的倍数的地址开始但是对齐规则必须递归地应用于每个结构成员。 3 元素向量与 4 元素向量具有相同的对齐方式。这意味着:
Position
成员以与每个数组成员相同的对齐方式开始Velocity
、IPosition
和IVelocity
成员必须在给定数组元素开始后的 16 字节的倍数处开始。LifeTime
和ILifeTime
成员具有 4 字节对齐。
因此您的结构的总大小(以字节为单位)等于:
Position
- 16 字节(Position
本身占用 12 字节,但下一个成员有 16 字节对齐)Velocity
- 16 字节IPosition
- 16 字节IVelocity
+LifeTime
- 16 字节ILifeTime
- 4 个字节
这给出了 68 个字节。因此,据我了解,您需要在结构末尾填充 12 个字节(数组元素之间额外 12 个字节),因为每个数组元素必须从 16 的倍数的地址开始。
因此第一个数组元素从绑定到存储缓冲区的内存的偏移量 0 开始。但是第二个数组元素必须从内存开始的偏移量 80 开始(大于 68 的 16 的最接近倍数)等等。
或者,正如@NicolBolas 评论的那样,为了让生活更轻松,只将所有内容打包到 vec4 成员中 ;-)。
虽然不完全正确但更好:
在你的例子中,你结构的最大成员是 vec3(浮点数的三元素向量)。因此,您的数组元素的基本对齐方式等于 12 个字节(对于 std430 布局中的结构数组,基本对齐方式不必四舍五入为 4 元素向量的 mach 对齐方式。< - 在这里我错了。我们不必对结构的基对齐进行舍入,但其成员的对齐是正常计算的,vec3 对齐与 vec4 对齐相同)。这意味着 Your array 的每个元素都必须从 12 的倍数的地址开始(不,在这种情况下它应该从 16 的倍数开始)。
但是对齐规则必须递归地应用于每个结构成员。 3 元素向量与 4 元素向量具有相同的对齐方式。这意味着:
Position
成员以与每个数组成员相同的对齐方式开始Velocity
、IPosition
和IVelocity
成员必须在给定数组元素开始后的 16 字节的倍数处开始。LifeTime
和ILifeTime
成员具有 4 字节对齐。
因此您的结构的总大小(以字节为单位)等于:
Position
- 16 字节(Position
本身占用 12 字节,但下一个成员有 16 字节对齐)Velocity
- 16 字节IPosition
- 16 字节IVelocity
+LifeTime
- 16 字节ILifeTime
- 4 个字节
这给出了 68 个字节。因此,据我了解,您需要在结构末尾进行 4 字节填充(数组元素之间额外 4 字节),因为每个数组元素必须从 12 的倍数地址开始(同样,我们在这里需要 12 字节的填充,以便下一个数组元素从 16 的倍数开始,而不是 12).
因此第一个数组元素从绑定到存储缓冲区的内存的偏移量 0 开始。但是第二个数组元素必须从内存开始的偏移量 72 开始(大于 68 的 12 的最接近倍数)等等。
前一个错误答案:
在您的例子中,最大的成员是 vec3(浮点数的三元素向量)。它的对齐等于 12 个字节(在结构数组的情况下,我们不必将 3 元素向量的对齐舍入到 4 元素向量的 mach 对齐)。 Your struct 的大小(以字节为单位)等于 56。因此,据我了解,您需要在结构末尾填充 4 个字节(数组元素之间还有 4 个字节),因为每个数组元素必须从以下地址开始是 12 的倍数。