Vulkan Tutorial 25 Images

操作系统:Windows8.1

显卡:Nivida GTX965M

开发工具:Visual Studio 2017


Introduction

至目前为止,几哪图形应用每个终端颜色进行着色处理,这是一个局限性比较老之措施。在本教程的一样片段内容遭,我们落实纹理映射,使得几何图形看起更生动有趣。这为会见同意我们以未来的回中加载与制图基本的3D模型。

 

加上一个纹理贴图及应用程序需要以下几只步骤:

  • 创建设备内存支持之图像对象
  • 起图像文件填充像素
  • 创造图像采样器
  • 加上组合的图像采样器描述符,并起纹理采样颜色信息

俺们事先就用过图像对象,但是她都是由于交换链扩展自动创建的。这次咱们且自己创立。创建一个图像和填充数据与事先的顶峰缓冲区创建类似。我们开始利用暂存资源并采取像素数量进行填空充,接着以该拷贝到结尾用于渲染使用的图像对象中。尽管可以啊这个创建一个暂存图像,Vulkan也同意打VkBuffer中拷贝像素到图像被,这片API在一部分硬件及格外有效率
faster on some
hardware
。我们第一会见创缓冲区并经诸如从进行填写充,接着创建一个图像对象拷贝像素。创建一个图像和创造缓冲区相仿。就像我们前看来底那么,它需查询内存需求,分配设备外存并进行绑定。

 

然而,仍然有一些格外的工作要面对,当我们下图像的当儿。我们明白图像可以来不同之布局,它影响其实像从在内存中的社。由于图片硬件的做事原理,简单的逐行存储像从可能不是超级的性能选择。对图像执行外操作时,必须保证它们有最佳的布局,以便在拖欠操作着以。实际上我们已当指定渲染通道的时段看了这些布局项目:

  • VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: 用于表现,使用最佳
  • VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
    当使用附件从部分着色器进行勾勒副下,使用最佳
  • VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
    作为传送源操作的上,使用最佳,比如vkCmdCopyImageToBuffer
  • VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
    作为传输目的地的时候,使用最佳,比如vkCmdCopyBufferToImage
  • VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
    着色器中用于采样,使用最佳

变图像布局之尽广泛方式有是管线屏障 pipeline
barrier
。管线屏障主要用来共同访问资源,诸如确保图像于宣读之前写入,但是呢堪用于布局变换。在本章节被我们以见面视什么用管线屏障完成这个任务。除此之外,屏障也得用来VK_SHARING_MODE_EXCLUSIVE模式下队列簇宿主的转移。

Image library


用来加载图片的堆栈发生好多,甚至可以团结编写代码加载简单格式的图纸以BMP和PPM。在本教程中我们以见面使stb_image库房。优势是持有的代码都在单一的文件中,所以她不欲外困难的构建配置。下载stb_image.h头文件并以其保存在便民之职,在此我们存放和GLFW、GLM、vulkan头文件之一律之目录中
3rdparty\Include 下,如图所示:

Visual Studio

确认$(SolutionDir)\3rdparty\Include添加到 Additional Include
Directories
路径中。

Loading an image


含image库的头文件:

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

默认情况下头文件只定义了函数的原型。一个代码文件需要动用 STB_IMAGE_IMPLEMENTATION 概念包含头文件被定义之函数体,否则会收链接错误。

void initVulkan() {
    ...
    createCommandPool();
    createTextureImage();
    createVertexBuffer();
    ...
}

...

void createTextureImage() {

}

开创新的函数createTextureImage用于加载图片以及付到Vulkan图像对象被。我们呢会以命令缓冲区,所以用在createCommandPool其后调用。

 

shaders目录下新增加新的textures目录,用于存放贴图资源。我们将见面从目录中加载名也texture.jpg的图像。这里选择
CC0 licensed
image

并调整也512 x
512像从大小,但是当这边可以用任何你望的图。库支持广大主流的图样文件格式,比如JPEG,PNG,BMP和GIF。

下库加载图片是非常容易的:

void createTextureImage() {
    int texWidth, texHeight, texChannels;
    stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
    VkDeviceSize imageSize = texWidth * texHeight * 4;

    if (!pixels) {
        throw std::runtime_error("failed to load texture image!");
    }
}

stbi_load函数使用文件的门径和通道的数码作为参数加载图片。STBI_rgb_alpha价强制加载图片的alpha通道,即使她自己并未alpha,但是这么做对前加载其他的纹路的一致性非常友好。中间三只参数用于出口width,
height 和事实上的图纸通道数量。返回的指针是如素数组的首先独因素地址。总共
texWidth * texHeight * 4
个如素值,像从在STBI_rgba_alpha的情形下逐行排列,每个像素4个字节。

Staging buffer


俺们现在要是于host visible内存中创建一个缓冲区,以便我们得以采用vkMapMemory连拿如素复制给她。在createTextureImage函数中补充临时缓冲区变量。

VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;

缓冲区必须对host
visible内存可见,为者我们对它们进行映射,之后采用它当传输源拷贝像素到图像对象被。

createBuffer(imageSize, 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, imageSize, 0, &data);
memcpy(data, pixels, static_cast<size_t>(imageSize));
vkUnmapMemory(device, stagingBufferMemory);

毫无遗忘清理原图像的像素数量:

stbi_image_free(pixels);

Texture Image


尽管我们得以经过设置在色器访问缓冲区中的例如素值,但是于Vulkan中尽好利用image对象好该操作。图像对象足以允许我们采取二维坐标,从而再次爱的高效的追寻颜色。图像中的像素让成为纹素即纹理元素,我们用于此间开始利用该名。添加以下新的切近成员:

VkImage textureImage;
VkDeviceMemory textureImageMemory;

对图像的参数经VkImageCreateInfo结构体来描述:

VkImageCreateInfo imageInfo = {};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.width = static_cast<uint32_t>(texWidth);
imageInfo.extent.height = static_cast<uint32_t>(texHeight);
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;

imageType字段指定图像类型,告知Vulkan采用什么的坐标系在图像中采集纹素。它可是1D,2D跟3D图像。1D图像用于存储数组数还是灰度图,2D图像主要用以纹理贴图,3D图像用于存储立体纹素。extent字段指定图像的尺码,基本上每个轴及生略纹素。这便是怎深度必须是1而不是0。我们的纹理不见面是一个屡屡组,而本我们不见面使mipmapping功能。

imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;

Vulkan支持多图像格式,但无论如何我们要当缓冲区中也纹素应用以及像从一致的格式,否则拷贝操作会失败。

imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;

tiling字段可以设定两者有:

  • VK_IMAGE_TILING_LINEAR:
    纹素基于行主序的布局,如pixels数组
  • VK_IMAGE_TILING_OPTIMAL:
    纹素基于现实的落实来定义布局,以实现最佳访问

及图像布局不同之是,tiling模式不克在此后修改。如果需要在内存图像吃直接看纹素,必须动VK_IMAGE_TILING_LINEAR。我们用会使用暂存缓冲区代替暂存图像,所以这一部分不是老大有必不可少。为了还实惠的由shader中做客纹素,我们将会晤使VK_IMAGE_TILING_OPTIMAL

imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;

对于图像的initialLayout字段,仅发生个别独可卜的价:

  • VK_IMAGE_LAYOUT_UNDEFINED:
    GPU不可知动用,第一只转移将废弃弃纹素。
  • VK_IMAGE_LAYOUT_PREINITIALIZED:
    GPU不能够使,但是首先次于变将会见保留纹素。

几乎从不必要在率先次变时保留纹素。然而,一个例子是,如果您想将图像用作与
VK_IMAGE_TILING_LINEAR
布局相结合的暂存图像。在这种场面下,您要拿纹素数据上传到它们,然后以图像转换为污染输源,而非会见招致丢失数据。但是,在咱们的例子中,我们首先将图像转换为传输目标,然后由缓冲区目标复制纹理数据,因此我们不需要此属性,可以高枕无忧地运用
VK_IMAGE_LAYOUT_UNDEFINED

imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;

这里的 usage 字段与缓冲区创建进程被应用的 usage
字段有同等之语意。图像将见面被当缓冲区拷贝的对象,所以应安装作为传输目的地。我们尚期待从着色器中访问图像对咱的mesh进行着色,因此实际的usage还要包括VK_IMAGE_USAGE_SAMPLED_BIT

imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

 因为图像会于一个拔列簇中动用:支持图形或者传输操作。

imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.flags = 0; // Optional

samples表明位以及大多又采样相关。这仅适用于当附件的图像,所以我们坚持一个采样数值。与稀疏图像相关的图像发一些只是摘的标志。稀疏图像是单某些区域实际被存储器支持之图像。例如,如果下3D纹理进行立体地形,则可采取这个方法来避免分配内存来囤大量“空气”值。我们不会见以本教程中以,所以设置默认值0

if (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS) {
    throw std::runtime_error("failed to create image!");
}

使用vkCreateImage开创图像,这里没其他异常的参数设置。可能图形硬件不支持VK_FORMAT_R8G8B8A8_UNORM格式。我们应有有着一个可以代表的好承受之列表。然而对这种特定格式的支撑是老大的,我们以会见过了这同步。使用不同之格式为需繁琐的转换过程。我们会返回深度缓冲区章节,实现类似之网。

VkMemoryRequirements memRequirements;
vkGetImageMemoryRequirements(device, textureImage, &memRequirements);

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

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

vkBindImageMemory(device, textureImage, textureImageMemory, 0);

为图像工作分配内存与为缓冲区分配内存是相近的,使用vkGetImageMemoryRequirements代替vkGetBufferMemoryRequirements,并使用vkBindImageMemory代替vkBindBufferMemory

 

这函数已经换得较庞大臃肿了,而且亟需以后面的章中开创更多之图像,所以我们当拿图像创建抽象成一个createImage函数,就如前也buffers缓冲区做的政工一样。创建函数并以图像对象的创办和内存分配活动过来:

void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) {
    VkImageCreateInfo imageInfo = {};
    imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    imageInfo.imageType = VK_IMAGE_TYPE_2D;
    imageInfo.extent.width = width;
    imageInfo.extent.height = height;
    imageInfo.extent.depth = 1;
    imageInfo.mipLevels = 1;
    imageInfo.arrayLayers = 1;
    imageInfo.format = format;
    imageInfo.tiling = tiling;
    imageInfo.initialLayout =VK_IMAGE_LAYOUT_UNDEFINED;
    imageInfo.usage = usage;
    imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
    imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

    if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) {
        throw std::runtime_error("failed to create image!");
    }

    VkMemoryRequirements memRequirements;
    vkGetImageMemoryRequirements(device, image, &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, &imageMemory) != VK_SUCCESS) {
        throw std::runtime_error("failed to allocate image memory!");
    }

    vkBindImageMemory(device, image, imageMemory, 0);
}

这边用了width, height, format, tiling mode, usage和memory
properties参数,因为这些参数根据教程被开创的图像而各异。

 

createTextureImage函数现在简化为:

void createTextureImage() {
    int texWidth, texHeight, texChannels;
    stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
    VkDeviceSize imageSize = texWidth * texHeight * 4;

    if (!pixels) {
        throw std::runtime_error("failed to load texture image!");
    }

    VkBuffer stagingBuffer;
    VkDeviceMemory stagingBufferMemory;
    createBuffer(imageSize, 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, imageSize, 0, &data);
        memcpy(data, pixels, static_cast<size_t>(imageSize));
    vkUnmapMemory(device, stagingBufferMemory);

    stbi_image_free(pixels);

    createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory);
}

Layout transitions


咱俩将要编写的函数会涉及到记录和执行命令缓冲区,所以现在适当的移除一些逻辑到扶助函数中错过:

VkCommandBuffer beginSingleTimeCommands() {
    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);

    return commandBuffer;
}

void endSingleTimeCommands(VkCommandBuffer commandBuffer) {
    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未遭已有的代码。现在足简化函数如下:

void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
    VkCommandBuffer commandBuffer = beginSingleTimeCommands();

    VkBufferCopy copyRegion = {};
    copyRegion.size = size;
    vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);

    endSingleTimeCommands(commandBuffer);
}

要是仍继续行使缓冲区,我们得以编写一个函数记录以及推行vkCmdCopyBuffeToImage来形成这个工作,但首先命令要求图像于对的布局中。创建一个初的函数处理布局变换:

void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) {
    VkCommandBuffer commandBuffer = beginSingleTimeCommands();

    endSingleTimeCommands(commandBuffer);
}

通常主流的做法用于拍卖图像变换是以 image memory
barrier
。一个管线的屏障通常用于访问资源的时节进行联合,也接近缓冲区在念操作前形成写入操作,当然也得用来图像布局的变换以及当运用 VK_SHARING_MODE_EXCLUSIVE 模式情况下,传输队列簇宿主的易。缓冲区时有发生一个抵的
buffer memory barrier

VkImageMemoryBarrier barrier = {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = oldLayout;
barrier.newLayout = newLayout;

前面片个参数指定布局变换。可以行使VK_IMAGE_LAYOUT_UNDEFINED作为oldLayout,如果无关心已经是与图像中的内容。

barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;

要对传输队列簇的宿主使用屏障,这点儿单参数需要设置队列簇的目。如果非关注,则要装VK_QUEUE_FAMILY_IGNORED(不是默认值)。

barrier.image = image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;

imagesubresourceRange指定受到震慑的图像和图像的一定区域。我们的图像不是数组,也并未应用mipmapping
levels,所以特指定一级,并且一个叠。

barrier.srcAccessMask = 0; // TODO
barrier.dstAccessMask = 0; // TODO

屏蔽主要用以共同目的,所以要于用屏障前指定哪一样栽操作类型和干到的资源,同时使指定哪一样栽操作与资源要等屏障。我们须这样做尽管我们利用vkQueueWaitIdle人工的决定并。正确的价值在旧的及新的布局,所以我们只要我们了解了要用的易,就足以回去布局有。

vkCmdPipelineBarrier(
    commandBuffer,
    0 /* TODO */, 0 /* TODO */,
    0,
    0, nullptr,
    0, nullptr,
    1, &barrier
);

有品类的管线屏障都应用同样的函数提交。命令缓冲区参数后底率先单参数指定管线的哪位阶段,应用屏障同步之前如果尽之放权操作。第二单参数指定操作将于屏障上等候的管线等。在屏障之前跟事后允许指定管线等在在屏障之前与后怎样运用资源。允许的值列在标准的
table报表中。比如,要当屏障之后由
uniform
中读取,您将点名使用 VK_ACCESS_UNIFORM_READ_BIT 以及初始着色器从
uniform 中读取作为管线等,例如
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT。为这种类型的指定非在色器管线等是不曾意义之,并且当指定同动用项目不配合的管线等时,validation
layer 将见面唤醒警示信息。

 

其三只参数可以设置为0或者VK_DEPENDENCY_BY_REGION_BIT。后者将屏障变换为每个区域的状态。这意味,例如,允许已经写了资源的区域开始念的操作,更加密切之粒度。

 

终极三独参数引用管线屏障的高频组,有三种植类型,第一种 memory
barriers,第二栽, buffer memory barriers, 和 image memory
barriers。第一种植就是是咱运用的。需要留意的是咱们尚无运用VkFormat参数,但是我们见面在深缓冲区章节中运用其举行一些例外之变。

Copying buffer to image


今日回去createTextureImage函数,我们编辑新的扶函数copyBufferToImage

void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) {
    VkCommandBuffer commandBuffer = beginSingleTimeCommands();

    endSingleTimeCommands(commandBuffer);
}

就如缓冲区拷贝一样,我们要指定拷贝具体哪有交图像的区域。这有的由此VkBufferImageCopy结构体描述:

VkBufferImageCopy region = {};
region.bufferOffset = 0;
region.bufferRowLength = 0;
region.bufferImageHeight = 0;

region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;

region.imageOffset = {0, 0, 0};
region.imageExtent = {
    width,
    height,
    1
};

大部分之字段已经字面意思很明亮了了。bufferOffset字段指定缓冲区中之byte偏移量,代表像素值起始的位置。bufferRowLengthbufferImageHeight字段指定像从在内存中的布局。比如可能以图像的实施及履行内填充有空字节。为彼此指定0代表如从紧密排列,这吗是咱应用的设置。imageSubresourceimageOffset
imageExtent字段指定我们且拷贝图像的呀有像素。

 

缓冲区拷贝到图像的操作以会晤动vkCmdCopyBufferToImage函数到行列中:

vkCmdCopyBufferToImage(
    commandBuffer,
    buffer,
    image,
    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
    1,
    &region
);

季个参数指定当前图像使用的布局。我们若图像为像素拷贝就变为optimal最佳的布局。现在我们特拷贝像素快到一个一体化的图像被,但是呢可以指定VkBufferImageCopy多次组,以便在一个操作着施行打缓冲区顶图像的异之正片操作。

Preparing the texture image


我们既形成了利用贴图图像的拥有工作,现在归来 createTextureImage 函数。最后一个工作是开创贴图图像texture
image。下一致步copy暂存缓冲区暨贴图图像。这要涉及个别独步骤:

  • 转换贴图图像及 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
  • 实践缓冲区到图像的正片操作

当即有的可比便于,如函数中所显示:

transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight));

图像是动 VK_IMAGE_LAYOUT_UNDEFINED 布局创建的,因此当转换
textureImage
时候应该指定wield旧布局。请记住,我们可以这样做,因为咱们在实行复制操作前未体贴其的情。

 

在shader着色器中开由贴图图像的采样,我们要最终一个移来准备着色器访问:

transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);

Transition barrier masks


要应用程序开启validation layers运行,你用会见看其唤醒
transitionImageLayout
中的走访掩码和管线等无效。我们还是需要根据变换着之布局设置它们。

 

有少种植转移需要处理:

  • Undefined → transfer destination: 传输写副操作不需要等其他业务
  • Transfer destination → shader reading:
    着色器读取操作应该等传输写副,特别是 fragment
    shader进行读取,因为就是咱若以纹理的地方。

这些规则下以下访问掩码和管线等展开点名:

VkPipelineStageFlags sourceStage;
VkPipelineStageFlags destinationStage;

if (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;
    destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
    barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
    barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;

    sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
    destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
} else {
    throw std::invalid_argument("unsupported layout transition!");
}

vkCmdPipelineBarrier(
    commandBuffer,
    sourceStage, destinationStage,
    0,
    0, nullptr,
    0, nullptr,
    1, &barrier
);

如齐所示,传输写副得在管线传输等展开。由于写副不必等其他事情,您可以指定一个空的顾掩码和极致早的或的管线等
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT 作为预屏障操作。

 

图像将被描绘副相同之流程阶段,随后出于片着色器读取,这就是为何我们在部分着色器管线等遭遇指定着色器读取访问的原由。

 

假使未来咱们要举行还多的换,那么我们将扩展这个作用。应用程序现在应有好成功运行,尽管目前没有其余可视化的变动。

 

欲注意的是,命令缓冲区提交会在开经常造成隐式
VK_ACCESS_HOST_WRITE_BIT 同步。由于 transitionImageLayout
函数单单行使单个命令执行命令缓冲区,因此而当布局转换中待
VK_ACCESS_HOST_WRITE_BIT 依赖关系,则可以使用这隐式同步将
srcAccessMask 设置为 0
。如果您想如果分明的话,这有赖于你,但自己个人并无是因这些OpenGL类似的
“隐式” 操作的粉丝。

 

骨子里为来同一栽通用的图像布局项目支持有的操作,VK_IMAGE_LAYOUT_GENERAL。问题是,它没有呢其它操作提供最佳的性表现。在某些特殊的状下得采取,例如使用图像作为输入和出口,或者在离预初始化布局后读博图像。

 

至目前为止,所有用于提交命令的拉函数已经给安装为经过等待队列化空闲来同步实施。对于实际行使,建议于单个命令缓冲区中组成这些操作,并异步方式履行其赢得更胜的吞吐量,尤其以createTextureImage函数中之变与拷贝操作。尝试通过创设一个setupCommandBuffer拉函数记录命令,并加上一个flushSetupCommands函数来施行所以就目录的下令。最好以纹理贴图映射工作晚开展,以检讨纹理资源是否还是是安装。

Cleanup


createTextureImage函数最后清理暂存缓冲区和分配的内存:

transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);

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

先后中应用的贴图图像直到退出的早晚以清理:

void cleanup() {
    cleanupSwapChain();

    vkDestroyImage(device, textureImage, nullptr);
    vkFreeMemory(device, textureImageMemory, nullptr);

    ...
}

如今图像包含了贴图,但是图管线需要一个门道看它。我们见面当生一样章节讨论。

 

型代码 GitHub地址。

相关文章