Vulkan渲染到存储纹理后读取异常问题求助
问题描述
我创建了一个存储纹理,将屏幕显示的相同片段数据存入其中,但在另一个管线的片段着色器中读取该纹理时无法正常工作。
使用了相同的Render Pass和Pipeline Layout,执行流程为:
- 启动Render Pass
- 执行第一个管线
- 结束Render Pass
- 为存储纹理执行
vkCmdPipelineBarrier - 再次启动同一Render Pass
- 执行第二个管线
- 结束Render Pass
现象:实际显示效果与预期不符,画面存在闪烁。
相关代码
1. Render Pass间的屏障同步代码
VkImageMemoryBarrier imageMemoryBarrier = {}; imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; // We won't be changing the layout of the image imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; imageMemoryBarrier.image = outlineTexture->getTeximage(); imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; vkCmdPipelineBarrier( commandBuffer, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
2. 纹理创建流程代码
VkFormatProperties formatProperties; vkGetPhysicalDeviceFormatProperties(creDevice.physical_Device(), VK_FORMAT_R8G8B8A8_UNORM, &formatProperties); // Check if requested image format supports image storage operations assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT); VkImageCreateInfo imageCreateInfo{}; imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; imageCreateInfo.extent = { (uint32_t)texWidth, (uint32_t)texHeight, 1 }; imageCreateInfo.mipLevels = 1; imageCreateInfo.arrayLayers = 1; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; // Image will be sampled in the fragment shader and used as storage target in the compute shader imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT; imageCreateInfo.flags = 0; // If compute and graphics queue family indices differ, we create an image that can be shared between them // This can result in worse performance than exclusive sharing mode, but save some synchronization to keep the sample simple /* std::vector<uint32_t> queueFamilyIndices; if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) { queueFamilyIndices = { vulkanDevice->queueFamilyIndices.graphics, vulkanDevice->queueFamilyIndices.compute }; imageCreateInfo.sharingMode = VK_SHARING_MODE_CONCURRENT; imageCreateInfo.queueFamilyIndexCount = 2; imageCreateInfo.pQueueFamilyIndices = queueFamilyIndices.data(); } */ QueueFamilyIndices indices = creDevice.findPhysicalQueueFamilies(); uint32_t queueFamilyIndices[] = { indices.graphicsFamily, indices.presentFamily }; if (indices.graphicsFamily != indices.presentFamily) { imageCreateInfo.sharingMode = VK_SHARING_MODE_CONCURRENT; imageCreateInfo.queueFamilyIndexCount = 2; imageCreateInfo.pQueueFamilyIndices = queueFamilyIndices; } else { imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageCreateInfo.queueFamilyIndexCount = 0; // Optional imageCreateInfo.pQueueFamilyIndices = nullptr; // Optional } //imageCreateInfo.sharingMode = VK_SHARING_MODE_CONCURRENT; //imageCreateInfo.queueFamilyIndexCount = 2; //imageCreateInfo.pQueueFamilyIndices = queueFamilyIndices; creDevice.createImageWithInfo(imageCreateInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); if (auto commandBuffer = creDevice.beginSingleTimeCommands()) { VkImageSubresourceRange subresourceRange = {}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = 0; subresourceRange.levelCount = 1; subresourceRange.layerCount = 1; VkImageMemoryBarrier imageMemoryBarrier{}; imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; imageMemoryBarrier.image = textureImage; imageMemoryBarrier.subresourceRange = subresourceRange; imageMemoryBarrier.srcAccessMask = 0; vkCmdPipelineBarrier( commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier); creDevice.endSingleTimeCommands(commandBuffer); } // Create sampler VkSamplerCreateInfo sampler{}; sampler.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; sampler.magFilter = VK_FILTER_LINEAR; sampler.minFilter = VK_FILTER_LINEAR; sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; sampler.addressModeV = sampler.addressModeU; sampler.addressModeW = sampler.addressModeU; sampler.mipLodBias = 0.0f; sampler.maxAnisotropy = 1.0f; sampler.compareOp = VK_COMPARE_OP_NEVER; sampler.minLod = 0.0f; sampler.maxLod = creDevice.msaaSamples; //sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; if (vkCreateSampler(creDevice.device(), &sampler, nullptr, &textureSampler) != VK_SUCCESS) { throw std::runtime_error("failed to create texture sampler!"); }; // Create image view VkImageViewCreateInfo view{}; view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; view.image = VK_NULL_HANDLE; view.viewType = VK_IMAGE_VIEW_TYPE_2D; view.format = VK_FORMAT_R8G8B8A8_UNORM; view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; view.image = textureImage; if (vkCreateImageView(creDevice.device(), &view, nullptr, &textureImageView) != VK_SUCCESS) { throw std::runtime_error("failed to create texture image view!"); }
3. 写入存储纹理的着色器代码
#version 450 layout (location = 0) in vec3 fragColor; layout (location = 1) in vec2 screenSize; layout (set = 0, binding = 2, rgba8) uniform image2D otlImage; layout (set = 1, binding = 1) uniform sampler2D texSampler; layout (location = 0) out vec4 outColor; void main() { outColor = vec4(fragColor, 1.0) * texture(texSampler, fragTextureCoord); imageStore(otlImage, ivec2((gl_FragCoord.x + 0.5)/screenSize.x, (gl_FragCoord.y + 0.5)/screenSize.y), outColor); }
4. 读取存储纹理的着色器代码
"""Vertex shader""" #version 450 layout (location = 0) out vec2 outUV; void main() { outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); gl_Position = vec4(outUV * 2.0f + -1.0f, 0.0f, 1.0f); } """Fragment Shader""" #version 450 layout (location = 0) in vec2 outUV; layout (location = 0) out vec4 outColor; layout (set = 0, binding = 2, rgba8) uniform image2D otlImage; void main() { outColor = imageLoad(otlImage, ivec2(outUV)); }
问题排查与修复
1. 屏障同步阶段错误
当前vkCmdPipelineBarrier的srcStageMask设为VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,但第一个管线是在片段着色器中执行imageStore写入纹理,写入操作属于片段着色器阶段,而非顶点着色器。正确的srcStageMask应该是VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,确保所有片段着色器的写入操作完成后再允许读取。
修改后的屏障代码:
vkCmdPipelineBarrier( commandBuffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, // 修正源阶段为片段着色器 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
2. 纹理坐标计算错误
写入纹理时的坐标转换逻辑错误:
ivec2((gl_FragCoord.x + 0.5)/screenSize.x, (gl_FragCoord.y + 0.5)/screenSize.y)
gl_FragCoord的坐标范围是[0, width]和[0, height],除以screenSize.x/y(即宽高)后得到的是[0,1]的归一化坐标,但imageStore需要的是像素索引(整数坐标),正确的计算应该直接取gl_FragCoord的整数部分:
修改写入着色器的坐标计算:
imageStore(otlImage, ivec2(gl_FragCoord.xy), outColor);
3. 读取阶段的坐标范围不匹配
读取着色器中,outUV的范围是[0,2](顶点着色器生成的坐标是(0,0),(2,0),(0,2)),直接转换为ivec2(outUV)会得到远超纹理分辨率的坐标。正确的做法是将outUV归一化到[0,1]后,乘以纹理的实际宽高得到像素索引:
首先修改顶点着色器生成[0,1]范围的UV:
void main() { outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2) * 0.5f; // 归一化到[0,1] gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f); }
然后在片段着色器中,通过UBO传入纹理分辨率并计算像素坐标:
// 添加UBO绑定纹理分辨率 layout(set = 0, binding = 3) uniform TextureSize { vec2 size; }; void main() { outColor = imageLoad(otlImage, ivec2(outUV * size)); }
4. 闪烁问题的额外注意点
闪烁通常是因为帧间数据未正确同步,或者双缓冲纹理未正确切换。如果使用了交换链双缓冲,确保存储纹理的写入和读取是针对当前帧的正确纹理实例,或者在帧间添加足够的同步屏障,避免前一帧的数据干扰当前帧。
内容的提问来源于stack exchange,提问作者Akshayakeerthi Duraisamy

