创建VkImage和VkImageView
在SceneWidget.h中添加接口bool CreateTextureImageView(),VkImage只是保存了图片的资源和数据,要想展示出来,就必需得有VkImageView。在这个函数里创建,程序结束时还要清理,所以需要声明两个成员变量,保存生成的VkImage和VkImageView的对象。
添加成员:VkImage m_image = VK_NULL_HANDLE; VkImageView m_imageView = VK_NULL_HANDLE,每个VkImage对象的创建还对应一个 VkDeviceMemory。所以还有再添加一个成员 VkDeviceMemory m_ImageMemory = VK_NULL_HANDLE。
按vulkan编程指南教程中的思路,需要先加载一个张图片。在电脑上找到一张图
文件名为test.png。先放到工程目录下。
然后可以CreateTextureImageView函数里加载这个图,利用Qt的QImage类,进行加载。
QImage image;
image.load(“test.png”);
来进行加载。
然后通过
QImage::Format format = image.format();
unsigned char* pixelData = image.bits();
来获取像素格式和rgba的数据。
然后申请临时的VkBuffer imageBuffer = VK_NULL_HANDLE;
和临时的VkDeviceMemory imageMemory = VK_NULL_HANDLE;
调用CreateBuffer,参数为:
第一个参数:像素通道数*图片的宽*图片的高。这里是4*image.width()*image.height()数据类型为VkDeviceSize类型。
第二个参数:VK_BUFFER_USAGE_TRANSFER_SRC_BIT,是映射到cpu端的内存,做为源数据缓存使用。
第三个参数:VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT在cpu端的内存,对显卡可见,且访问时不需要特殊操作。
第四个参数:为imageBuffer。
第五个参数:为imageMemory
通过vkMapMamory把数据映射到void* data上。再将cpu的数据memcpy到GPU显存中。解除映射。通过图片的数据信息创建m_image和m_imageMemory。即调用:
CreateImage(),其参数:
第一个参数:image.width()图片的宽
第二个参数:image.height()图片的高。
第三个参数:图片可类似与mipmap,存有多个图层,这个参数表示当前所在的图层,这个图没有分层级,这里设置为1.
第四个参数:采样数,VkSampleCountFlagBits的值,这里取VK_SAMPLE_COUNT_1_BIT,尽可能小的采样,使图片保真。
第五个参数:图片的像素格式。这里为VK_FORMAT_R8G8B8A8_SRGB
第六个参数:图片的平铺方式。这里为VK_IMAGE_TILING_OPTIMAL
第七个参数:图片的例用,可做为源和目的数据,且可用于采样。
第八个参数:这里是从本地取图片数据。
第九个参数:m_image。取出生成的image对象。
第十个参数:m_imageMemory。
还是和vertexBuffer的处理一样,要通过copyBuffer这个运作将图片的数据拷贝到显卡来直接使用。
但是图片的数据,要使用copy函数,就得将内存的布局变换一下,所以需要再添加一个辅助函数:void TransitionImageLayout()函数,参数
第一个参数:要转换的VkImage对象
第二个参数:VkImage的像素格式。
第三个参数:VkImage原有的内存布局
第四个参数:要转换后的内存布局。
第五个参数:当前VkImage所在的图层的层级。
在CreateTextureImageView调用这个TransitionImageLayout(),传入参数:
第一个参数:m_image
第二个参数:VK_FORMAT_R8G8B8A8_SRGB,是这个图片当前的像素格式。
第三个参数:VK_IMAGE_LAYOUT_UNDEFINED,获得更好的性能表现。
第四个参数:VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,转换成适合输出到目的的内存布局。
第五个参数:1,如果图片中有包含多层级数据,这个值就要根据真实的层级来输入了。
然后就是将imageBuffer中的数据拷贝到m_image中,再添加一个辅助函数:void CopyBufferToImage(),参数为:
第一个参数:要拷贝的imageBuffer
第二个参数:m_image,
第三个参数:图片的宽
第四个参数:图片的高
拷贝完成后,清理imageBuffer和对应的Memory。
vkDestroyBuffer(m_device, imageBuffer, nullptr);
vkFreeMemory(m_device, imageMemory, nullptr);
函数的最后,调用CreateImageView接口,生成m_imageView.
最后就是提交image.还要再添加一个函数SubmmitImage()按vulkan编程指南上讲解的,还要设置Barrier,所需要参数:
第一个参数:m_image
第二个参数:像素格式,VK_FORMAT_R8G8B8A8_SRGB
第三个参数:图片的宽
第四个参数:图片的高
现在需要补齐刚刚添加的这三个函数:
TransitionImageLayout:
内存布局的转换,是通过设置VkImageMemoryBarrier数据结构,并调用vkCmdPipelineBarrier函数进行转换。
先定义一个VkImageMemoryBarrier barrier = {}并赋值:
固定写法
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
保持默认
barrier.pNext = nullptr;
要转换的布局
barrier.oldLayout = oldLayout;
转换后的布局
barrier.newLayout = newLayout;
不需要转移队列的所有权。
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
要转换的image。
barrier.image = image;
设置subresourceRange,与颜色缓存绑定、层级从0开始、层的总数。
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = mipLevels;
第一个arrayLayer
barrier.subresourceRange.baseArrayLayer = 0;
arrayLayer的总数
barrier.subresourceRange.layerCount = 1;
在vkCmdPipelineBarrier函数中还有两个参数为VkPipelineStageFlags类型,一个是源数据的,一个是目的数据的。现在定义两个变量VkPipelineStageFlags sourceStage, destStage;
sourceStage, destStage两个的值和barrier.srcAccessMask, barrier.dstAccessMask;的值根据参数中调用时传入的oldLayout和newLayout有关,由于之前输入时,
oldLayout = VK_IMAGE_LAYOUT_UNDEFINED
newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
所以
barrier.srcAccessMask = 0, barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT
队列最开始阶段时,可接受命令
sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
支持copy命令
destStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
还差一个commandBuffer,通过之前封装的BeginSingleTimeCommands函数来取得。
VkCommandBuffer commandBuffer = BeginSingleTimeCommands()
参数设置完成,调用vkCmdPipelineBarrier()其参数:
第一个参数:commandBuffer,
第二个参数:sourceStage
第三个参数:destStage
第四个参数:不需要,为0
第五个参数:不需要,为0
第六个参数:为nullptr.
第七个参数:为0
第八个参数:为nullptr.
第九个参数:为1
第十个参数:为&barrier
函数最后,调用EndCommands结束提交。
CopyBufferToImage:
在这个函数,调用vkCmdCopyBufferToImage进行拷贝操作,其参数:
第一个参数commandBuffer,可以像上一个函数一样,通过BeginCommands获取。
第二个参数:使用函数传入的参数buffer
第三个参数:使用函数传入的参数:image
第四个参数:之前都是用的VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,这里保持一致
第五个参数:这里拷贝一张图片,设置为1
第六个参数:VkBufferImageCopy类型的指针,所以这里补充一个变量VkBufferImageCopy region = {}
region属性的赋值:
在数据buffer中和偏移,这里为0
region.bufferOffset = 0;
bufferRowLength和bufferImageHeight设置为0,图片的范围根据imageExtent计算
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
与颜色缓冲区绑定
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
层级为0
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
共一层
region.imageSubresource.layerCount = 1;
图片的偏移为000
region.imageOffset = { 0, 0, 0 };
图片的宽高,二维图片深度默认为1
region.imageExtent = {width, height, 1};
与BeginCommands对应,调用EndCommands
SubmmitImage:
先检查设备,是否支持线性采样,可通过vkGetPhysicalDeviceFormatProperties查找采样属性,检查是否支持VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT特性。
其参数:
第一个参数:为物理设备对象,m_physicalDevice
第二个参数:函数的参数,format.
第三个参数:VkFormatProperties类型的传出参数formatProperties。
检查formatProperties.optimalTilingFeatures特性是否包含VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT,一般使用与操作。
同样在这个函数里,需要处理VkImageMemoryBarrier,调用vkCmdPipelineBarrier函数。
vkCmdPipelineBarrier函数的参数:
第一个参数:commandBuffer,同样从BeginCommands获取,
第二个参数:VK_PIPELINE_STAGE_TRANSFER_BIT源数据支持copy
第三个参数:VK_PIPELINE_STAGE_TRANSFER_BIT目的数据支持copy
第四个参数:不需要,设置为0
第五个参数:不需要设置为0
第六个参数:nullptr
第七个参数:0
第八个参数:nullptr.
第九个参数:VkImageMemoryBarrier数组的个数为1
第十个参数:VkImageMemoryBarrier类型的数组指针,需要加一个变量VkImageMemoryBarrier barrier = {};
其属性赋值:
固定写法
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
保持默认
barrier.pNext = nullptr;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = 0;
这是操作在目的数据上的,所以是:
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
只从shader里读取
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
还是VK_QUEUE_FAMILY_IGNORED;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseMipLevel = 0;
在函数最后,调用EndCommands
然后在SceneWidget::Init中添加调用,并在SceneWidget::UnInit中添加清理:
vkFreeMemory(m_device, m_imageMemory, nullptr);
vkDestroyImage(m_device, m_image, nullptr);
vkDestroyImageView(m_device, m_imageView, nullptr);
最后执行代码,看一下效果:
没有错误提示:)