Vulkan - 统一缓冲区未发送到着色器
Vulkan - Uniform Buffers Not Sent To Shader
在使用 C# 学习 Vulkan 教程时,我到了应该开始使用统一缓冲区的地步(您可以找到教程 here)
先展示相关代码,再说明问题。我有几个相关的结构:
unsafe struct Buffer
{
public SharpVulkan.Buffer buffer;
public DeviceMemory memory;
public readonly ulong size;
public Buffer(ulong size) : this()
{
this.size = size;
}
public void Destroy(Device device)
{
device.DestroyBuffer(buffer);
device.FreeMemory(memory);
}
}
struct MVPMatrices
{
public static MVPMatrices Identity { get; } = new MVPMatrices { model = Matrix4x4.Identity, view = Matrix4x4.Identity, projection = Matrix4x4.Identity };
public Matrix4x4 model;
public Matrix4x4 view;
public Matrix4x4 projection;
}
struct UniformBuffer
{
public Buffer buffer;
public DescriptorSet descriptorSet;
public void Destroy(Device device)
{
buffer.Destroy(device);
}
}
我认为它们都是不言自明的,但如果您需要更多信息,我可以更新问题。
接下来,在主程序中有几个我创建的相关字段:
static DescriptorSetLayout descriptorSetLayout;
static DescriptorPool descriptorPool;
static UniformBuffer mvpMatricesBuffer;
static readonly MVPMatrices[] mvpMatricesArray = new MVPMatrices[1];
static ref MVPMatrices MVPMatrices => ref mvpMatricesArray[0];
这些是我用于缓冲区的一些实用方法:
static Buffer CreateBuffer(ulong size, BufferUsageFlags usage, MemoryPropertyFlags memoryPropertyFlags = MemoryPropertyFlags.HostVisible | MemoryPropertyFlags.HostCoherent)
{
Buffer buffer = new Buffer(size);
BufferCreateInfo createInfo = new BufferCreateInfo
{
StructureType = StructureType.BufferCreateInfo,
SharingMode = SharingMode.Exclusive,
Size = size,
Usage = usage,
};
buffer.buffer = logicalDevice.CreateBuffer(ref createInfo);
logicalDevice.GetBufferMemoryRequirements(buffer.buffer, out MemoryRequirements memoryRequirements);
physicalDevice.GetMemoryProperties(out PhysicalDeviceMemoryProperties memoryProperties);
uint memoryTypeIndex = 0;
for (uint i = 0; i < memoryProperties.MemoryTypeCount; i++)
{
MemoryType* memoryType = &memoryProperties.MemoryTypes.Value0 + i;
if ((memoryRequirements.MemoryTypeBits & (1 << (int)i)) != 0 && memoryType->PropertyFlags.HasFlag(memoryPropertyFlags))
{
memoryTypeIndex = i;
break;
}
}
MemoryAllocateInfo allocateInfo = new MemoryAllocateInfo
{
StructureType = StructureType.MemoryAllocateInfo,
AllocationSize = memoryRequirements.Size,
MemoryTypeIndex = memoryTypeIndex,
};
buffer.memory = logicalDevice.AllocateMemory(ref allocateInfo);
logicalDevice.BindBufferMemory(buffer.buffer, buffer.memory, 0);
return buffer;
}
static void SetBufferData<T>(this Buffer buffer, T[] data)
where T : struct
{
ulong size = (ulong)(Marshal.SizeOf<T>() * data.Length);
if (size != buffer.size)
throw new ArgumentException("Size of buffer data must be the same as the size of the buffer!");
IntPtr memory = logicalDevice.MapMemory(buffer.memory, 0, size, MemoryMapFlags.None);
System.Buffer.MemoryCopy(Marshal.UnsafeAddrOfPinnedArrayElement(data, 0).ToPointer(), memory.ToPointer(), (uint)size, (uint)size);
logicalDevice.UnmapMemory(buffer.memory);
}
static T[] GetBufferData<T>(this Buffer buffer)
where T : struct
{
T[] result = new T[(int)buffer.size / Marshal.SizeOf<T>()];
IntPtr memory = logicalDevice.MapMemory(buffer.memory, 0, buffer.size, MemoryMapFlags.None);
System.Buffer.MemoryCopy(memory.ToPointer(), Marshal.UnsafeAddrOfPinnedArrayElement(result, 0).ToPointer(), (uint)buffer.size, (uint)buffer.size);
logicalDevice.UnmapMemory(buffer.memory);
return result;
}
static DescriptorSet AllocateDescriptorSet()
{
DescriptorSetLayout* setLayout = stackalloc DescriptorSetLayout[1];
*setLayout = descriptorSetLayout;
DescriptorSetAllocateInfo allocateInfo = new DescriptorSetAllocateInfo
{
StructureType = StructureType.DescriptorSetAllocateInfo,
DescriptorPool = descriptorPool,
DescriptorSetCount = 1,
SetLayouts = (IntPtr)setLayout,
};
DescriptorSet set = DescriptorSet.Null;
logicalDevice.AllocateDescriptorSets(ref allocateInfo, &set);
return set;
}
static UniformBuffer CreateUniformBuffer(ulong size, uint binding)
{
UniformBuffer buffer = new UniformBuffer
{
buffer = CreateBuffer(size, BufferUsageFlags.UniformBuffer),
descriptorSet = AllocateDescriptorSet(),
};
DescriptorBufferInfo bufferInfo = new DescriptorBufferInfo
{
Buffer = buffer.buffer.buffer,
Offset = 0,
Range = buffer.buffer.size,
};
WriteDescriptorSet descriptorWrite = new WriteDescriptorSet
{
StructureType = StructureType.WriteDescriptorSet,
BufferInfo = new IntPtr(&bufferInfo),
DescriptorCount = 1,
DescriptorType = DescriptorType.UniformBuffer,
DestinationArrayElement = 0,
DestinationBinding = binding,
DestinationSet = buffer.descriptorSet,
ImageInfo = IntPtr.Zero,
TexelBufferView = IntPtr.Zero,
};
logicalDevice.UpdateDescriptorSets(1, &descriptorWrite, 0, null);
return buffer;
}
一开始我还用一个值对其进行了初始化,但我创建了 Get/SetBufferData 方法,因此我可以在每一帧更新数据。这是在渲染之前在主循环中调用的函数:
static void UpdateApplication()
{
MVPMatrices.model = Matrix4x4.Identity;
MVPMatrices.view = Matrix4x4.Identity;
MVPMatrices.projection = Matrix4x4.Identity;
mvpMatricesBuffer.buffer.SetBufferData(mvpMatricesArray);
Console.WriteLine(mvpMatricesBuffer.buffer.GetBufferData<MVPMatrices>()[0].model);
Console.WriteLine(mvpMatricesBuffer.buffer.GetBufferData<MVPMatrices>()[0].view);
Console.WriteLine(mvpMatricesBuffer.buffer.GetBufferData<MVPMatrices>()[0].projection);
}
还有分配命令缓冲区的函数。真的没有必要展示整个东西,但基本上:我分配缓冲区,绑定一些缓冲区并绘制。在绘制之前,我将描述符集绑定到命令缓冲区,所以这是我为此使用的代码(你只需要知道 "buffer" 一个指向命令缓冲区的指针):
DescriptorSet* descriptorSets = stackalloc DescriptorSet[1];
*descriptorSets = mvpMatricesBuffer.descriptorSet;
buffer->BindDescriptorSets(PipelineBindPoint.Graphics, pipelineLayout, 0, 1, descriptorSets, 0, null);
现在有两个着色器。唯一相关的着色器是顶点着色器,所以这里是:
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(binding = 0) uniform MVPMatrices
{
mat4 model;
mat4 view;
mat4 projection;
} mvpMatrices;
layout(location = 0) in vec3 position;
layout(location = 1) in vec4 color;
layout(location = 0) out vec4 vertexColor;
void main()
{
gl_Position = mvpMatrices.projection * mvpMatrices.view * mvpMatrices.model * vec4(position, 1);
vertexColor = color;
}
那么问题是什么?
与图形一样:它不渲染。每当我删除乘以矩阵的部分时,它都可以正常工作。我也试过将颜色设置为模型矩阵的第二行(应该是绿色),但是没有用。
我在更新方法中显示的日志打印了矩阵缓冲区的数据,它们都是单位矩阵(就像它们应该的那样)。
也就是说buffer的数据没问题
不过,描述符集是另一回事。当我没有将设置布局绑定到管线布局时,我收到一条错误消息,指出着色器使用了一个描述符槽,但它没有在管线布局中声明。
这意味着设置布局被正确绑定。
如果我不创建描述符集,那么当我将它绑定到命令缓冲区时会出现错误。
这意味着描述符集被正确创建,因此描述符池也很好(否则我会得到一个错误)。
因此缓冲区和描述符集都有效。
我得出一个结论: 缓冲区和描述符集没有正确链接。奇怪的是链接两者的代码是 Device.UpdateDescriptorSets 调用。问题是,如果我没有将缓冲区信息传递给 DescriptorWrite 变量,或者如果我将空句柄传递给缓冲区信息,我会收到一条错误消息,指出描述符集从未更新过。
如果我不将描述符集传递给 DescriptorWrite 变量,情况也是一样。
这意味着它知道我更新了它,并且它知道我 正在 发送一个缓冲区,但数据仍然不知何故没有被发送到着色器。
在尝试调试了一个多星期之后,我可以说一切对我来说都很完美,而且我不知道问题出在哪里。这让我很绝望,所以我来了。
这是我第一次post在网站上,我不知道我是否给了你所有你需要的信息(尽管我觉得我给了太多),所以如果有什么post 有问题,请告诉我,我会尽力修复它。
显然所有与制服相关的代码都没有问题。由于某种原因,问题出在调试报告上。我真的不知道它与制服有什么关系,但是当我禁用它时,一切都很完美。
我决定做的只是在大部分情况下禁用调试报告,但能够通过简单地使用#define 定义一些东西来恢复它(当我需要 Vulkan 的一些反馈时)。这可能不是一个好的解决方案,但这是我能想到的最好的解决方案。
我遇到过类似的事情,现在还在努力追查原因。如果我在我的 Android 应用程序中启用 "VK_LAYER_LUNARG_core_validation" 验证层,我的统一缓冲区在着色器中为零。
还没有解决方法,但我想知道这是否是验证层或我的描述符集中的错误。
我可以启用其他验证层而不会出现此问题。
在使用 C# 学习 Vulkan 教程时,我到了应该开始使用统一缓冲区的地步(您可以找到教程 here) 先展示相关代码,再说明问题。我有几个相关的结构:
unsafe struct Buffer
{
public SharpVulkan.Buffer buffer;
public DeviceMemory memory;
public readonly ulong size;
public Buffer(ulong size) : this()
{
this.size = size;
}
public void Destroy(Device device)
{
device.DestroyBuffer(buffer);
device.FreeMemory(memory);
}
}
struct MVPMatrices
{
public static MVPMatrices Identity { get; } = new MVPMatrices { model = Matrix4x4.Identity, view = Matrix4x4.Identity, projection = Matrix4x4.Identity };
public Matrix4x4 model;
public Matrix4x4 view;
public Matrix4x4 projection;
}
struct UniformBuffer
{
public Buffer buffer;
public DescriptorSet descriptorSet;
public void Destroy(Device device)
{
buffer.Destroy(device);
}
}
我认为它们都是不言自明的,但如果您需要更多信息,我可以更新问题。 接下来,在主程序中有几个我创建的相关字段:
static DescriptorSetLayout descriptorSetLayout;
static DescriptorPool descriptorPool;
static UniformBuffer mvpMatricesBuffer;
static readonly MVPMatrices[] mvpMatricesArray = new MVPMatrices[1];
static ref MVPMatrices MVPMatrices => ref mvpMatricesArray[0];
这些是我用于缓冲区的一些实用方法:
static Buffer CreateBuffer(ulong size, BufferUsageFlags usage, MemoryPropertyFlags memoryPropertyFlags = MemoryPropertyFlags.HostVisible | MemoryPropertyFlags.HostCoherent)
{
Buffer buffer = new Buffer(size);
BufferCreateInfo createInfo = new BufferCreateInfo
{
StructureType = StructureType.BufferCreateInfo,
SharingMode = SharingMode.Exclusive,
Size = size,
Usage = usage,
};
buffer.buffer = logicalDevice.CreateBuffer(ref createInfo);
logicalDevice.GetBufferMemoryRequirements(buffer.buffer, out MemoryRequirements memoryRequirements);
physicalDevice.GetMemoryProperties(out PhysicalDeviceMemoryProperties memoryProperties);
uint memoryTypeIndex = 0;
for (uint i = 0; i < memoryProperties.MemoryTypeCount; i++)
{
MemoryType* memoryType = &memoryProperties.MemoryTypes.Value0 + i;
if ((memoryRequirements.MemoryTypeBits & (1 << (int)i)) != 0 && memoryType->PropertyFlags.HasFlag(memoryPropertyFlags))
{
memoryTypeIndex = i;
break;
}
}
MemoryAllocateInfo allocateInfo = new MemoryAllocateInfo
{
StructureType = StructureType.MemoryAllocateInfo,
AllocationSize = memoryRequirements.Size,
MemoryTypeIndex = memoryTypeIndex,
};
buffer.memory = logicalDevice.AllocateMemory(ref allocateInfo);
logicalDevice.BindBufferMemory(buffer.buffer, buffer.memory, 0);
return buffer;
}
static void SetBufferData<T>(this Buffer buffer, T[] data)
where T : struct
{
ulong size = (ulong)(Marshal.SizeOf<T>() * data.Length);
if (size != buffer.size)
throw new ArgumentException("Size of buffer data must be the same as the size of the buffer!");
IntPtr memory = logicalDevice.MapMemory(buffer.memory, 0, size, MemoryMapFlags.None);
System.Buffer.MemoryCopy(Marshal.UnsafeAddrOfPinnedArrayElement(data, 0).ToPointer(), memory.ToPointer(), (uint)size, (uint)size);
logicalDevice.UnmapMemory(buffer.memory);
}
static T[] GetBufferData<T>(this Buffer buffer)
where T : struct
{
T[] result = new T[(int)buffer.size / Marshal.SizeOf<T>()];
IntPtr memory = logicalDevice.MapMemory(buffer.memory, 0, buffer.size, MemoryMapFlags.None);
System.Buffer.MemoryCopy(memory.ToPointer(), Marshal.UnsafeAddrOfPinnedArrayElement(result, 0).ToPointer(), (uint)buffer.size, (uint)buffer.size);
logicalDevice.UnmapMemory(buffer.memory);
return result;
}
static DescriptorSet AllocateDescriptorSet()
{
DescriptorSetLayout* setLayout = stackalloc DescriptorSetLayout[1];
*setLayout = descriptorSetLayout;
DescriptorSetAllocateInfo allocateInfo = new DescriptorSetAllocateInfo
{
StructureType = StructureType.DescriptorSetAllocateInfo,
DescriptorPool = descriptorPool,
DescriptorSetCount = 1,
SetLayouts = (IntPtr)setLayout,
};
DescriptorSet set = DescriptorSet.Null;
logicalDevice.AllocateDescriptorSets(ref allocateInfo, &set);
return set;
}
static UniformBuffer CreateUniformBuffer(ulong size, uint binding)
{
UniformBuffer buffer = new UniformBuffer
{
buffer = CreateBuffer(size, BufferUsageFlags.UniformBuffer),
descriptorSet = AllocateDescriptorSet(),
};
DescriptorBufferInfo bufferInfo = new DescriptorBufferInfo
{
Buffer = buffer.buffer.buffer,
Offset = 0,
Range = buffer.buffer.size,
};
WriteDescriptorSet descriptorWrite = new WriteDescriptorSet
{
StructureType = StructureType.WriteDescriptorSet,
BufferInfo = new IntPtr(&bufferInfo),
DescriptorCount = 1,
DescriptorType = DescriptorType.UniformBuffer,
DestinationArrayElement = 0,
DestinationBinding = binding,
DestinationSet = buffer.descriptorSet,
ImageInfo = IntPtr.Zero,
TexelBufferView = IntPtr.Zero,
};
logicalDevice.UpdateDescriptorSets(1, &descriptorWrite, 0, null);
return buffer;
}
一开始我还用一个值对其进行了初始化,但我创建了 Get/SetBufferData 方法,因此我可以在每一帧更新数据。这是在渲染之前在主循环中调用的函数:
static void UpdateApplication()
{
MVPMatrices.model = Matrix4x4.Identity;
MVPMatrices.view = Matrix4x4.Identity;
MVPMatrices.projection = Matrix4x4.Identity;
mvpMatricesBuffer.buffer.SetBufferData(mvpMatricesArray);
Console.WriteLine(mvpMatricesBuffer.buffer.GetBufferData<MVPMatrices>()[0].model);
Console.WriteLine(mvpMatricesBuffer.buffer.GetBufferData<MVPMatrices>()[0].view);
Console.WriteLine(mvpMatricesBuffer.buffer.GetBufferData<MVPMatrices>()[0].projection);
}
还有分配命令缓冲区的函数。真的没有必要展示整个东西,但基本上:我分配缓冲区,绑定一些缓冲区并绘制。在绘制之前,我将描述符集绑定到命令缓冲区,所以这是我为此使用的代码(你只需要知道 "buffer" 一个指向命令缓冲区的指针):
DescriptorSet* descriptorSets = stackalloc DescriptorSet[1];
*descriptorSets = mvpMatricesBuffer.descriptorSet;
buffer->BindDescriptorSets(PipelineBindPoint.Graphics, pipelineLayout, 0, 1, descriptorSets, 0, null);
现在有两个着色器。唯一相关的着色器是顶点着色器,所以这里是:
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(binding = 0) uniform MVPMatrices
{
mat4 model;
mat4 view;
mat4 projection;
} mvpMatrices;
layout(location = 0) in vec3 position;
layout(location = 1) in vec4 color;
layout(location = 0) out vec4 vertexColor;
void main()
{
gl_Position = mvpMatrices.projection * mvpMatrices.view * mvpMatrices.model * vec4(position, 1);
vertexColor = color;
}
那么问题是什么?
与图形一样:它不渲染。每当我删除乘以矩阵的部分时,它都可以正常工作。我也试过将颜色设置为模型矩阵的第二行(应该是绿色),但是没有用。
我在更新方法中显示的日志打印了矩阵缓冲区的数据,它们都是单位矩阵(就像它们应该的那样)。
也就是说buffer的数据没问题
不过,描述符集是另一回事。当我没有将设置布局绑定到管线布局时,我收到一条错误消息,指出着色器使用了一个描述符槽,但它没有在管线布局中声明。
这意味着设置布局被正确绑定。
如果我不创建描述符集,那么当我将它绑定到命令缓冲区时会出现错误。
这意味着描述符集被正确创建,因此描述符池也很好(否则我会得到一个错误)。
因此缓冲区和描述符集都有效。
我得出一个结论: 缓冲区和描述符集没有正确链接。奇怪的是链接两者的代码是 Device.UpdateDescriptorSets 调用。问题是,如果我没有将缓冲区信息传递给 DescriptorWrite 变量,或者如果我将空句柄传递给缓冲区信息,我会收到一条错误消息,指出描述符集从未更新过。
如果我不将描述符集传递给 DescriptorWrite 变量,情况也是一样。
这意味着它知道我更新了它,并且它知道我 正在 发送一个缓冲区,但数据仍然不知何故没有被发送到着色器。
在尝试调试了一个多星期之后,我可以说一切对我来说都很完美,而且我不知道问题出在哪里。这让我很绝望,所以我来了。
这是我第一次post在网站上,我不知道我是否给了你所有你需要的信息(尽管我觉得我给了太多),所以如果有什么post 有问题,请告诉我,我会尽力修复它。
显然所有与制服相关的代码都没有问题。由于某种原因,问题出在调试报告上。我真的不知道它与制服有什么关系,但是当我禁用它时,一切都很完美。
我决定做的只是在大部分情况下禁用调试报告,但能够通过简单地使用#define 定义一些东西来恢复它(当我需要 Vulkan 的一些反馈时)。这可能不是一个好的解决方案,但这是我能想到的最好的解决方案。
我遇到过类似的事情,现在还在努力追查原因。如果我在我的 Android 应用程序中启用 "VK_LAYER_LUNARG_core_validation" 验证层,我的统一缓冲区在着色器中为零。
还没有解决方法,但我想知道这是否是验证层或我的描述符集中的错误。
我可以启用其他验证层而不会出现此问题。