Vulkan: clean up image layout management.

This fixes validation errors and makes a first pass at simplification.
VulkanTexture now tracks image layout using RangeMap, which paves the
way for further simplification.
This commit is contained in:
Philip Rideout
2022-01-18 08:43:19 -08:00
committed by Mathias Agopian
parent 2da215e8e7
commit c0ba260ddf
11 changed files with 181 additions and 172 deletions

View File

@@ -129,11 +129,9 @@ void VulkanBlitter::blitFast(VkImageAspectFlags aspect, VkFilter filter,
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
VkImageLayout srcLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
if (src.texture) {
srcLayout = mContext.getTextureLayout(src.texture->usage);
}
const VkImageLayout srcLayout = src.texture ?
getDefaultImageLayout(src.texture->usage) :
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
transitionImageLayout(cmdbuffer, {
src.image,
@@ -172,7 +170,7 @@ void VulkanBlitter::blitFast(VkImageAspectFlags aspect, VkFilter filter,
// Determine the desired texture layout for the destination while ensuring that the default
// render target is supported, which has no associated texture.
const VkImageLayout desiredLayout = dst.texture ?
mContext.getTextureLayout(dst.texture->usage) :
getDefaultImageLayout(dst.texture->usage) :
mContext.currentSurface->getColor().layout;
transitionImageLayout(cmdbuffer, blitterTransitionHelper({
@@ -267,7 +265,7 @@ void VulkanBlitter::blitSlowDepth(VkImageAspectFlags aspect, VkFilter filter,
// BEGIN RENDER PASS
// -----------------
const VkImageLayout layout = mContext.getTextureLayout(TextureUsage::DEPTH_ATTACHMENT);
const VkImageLayout layout = getDefaultImageLayout(TextureUsage::DEPTH_ATTACHMENT);
const VulkanFboCache::RenderPassKey rpkey = {
.depthLayout = layout,

View File

@@ -333,25 +333,6 @@ VkFormat VulkanContext::findSupportedFormat(utils::Slice<VkFormat> candidates,
return VK_FORMAT_UNDEFINED;
}
VkImageLayout VulkanContext::getTextureLayout(TextureUsage usage) const {
// Filament sometimes samples from depth while it is bound to the current render target, (e.g.
// SSAO does this while depth writes are disabled) so let's keep it simple and use GENERAL for
// all depth textures.
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
return VK_IMAGE_LAYOUT_GENERAL;
}
// Filament sometimes samples from one miplevel while writing to another level in the same
// texture (e.g. bloom does this). Moreover we'd like to avoid lots of expensive layout
// transitions. So, keep it simple and use GENERAL for all color-attachable textures.
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
return VK_IMAGE_LAYOUT_GENERAL;
}
// Finally, the layout for an immutable texture is optimal read-only.
return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
void VulkanContext::createEmptyTexture(VulkanStagePool& stagePool) {
emptyTexture = new VulkanTexture(*this, SamplerType::SAMPLER_2D, 1,
TextureFormat::RGBA8, 1, 1, 1, 1,

View File

@@ -72,7 +72,6 @@ struct VulkanContext {
uint32_t selectMemoryType(uint32_t flags, VkFlags reqs);
VkFormat findSupportedFormat(utils::Slice<VkFormat> candidates, VkImageTiling tiling,
VkFormatFeatureFlags features);
VkImageLayout getTextureLayout(TextureUsage usage) const;
void createEmptyTexture(VulkanStagePool& stagePool);
VkInstance instance;

View File

@@ -1412,8 +1412,8 @@ void VulkanDriver::stopCapture(int) {
void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
uint32_t width, uint32_t height, PixelBufferDescriptor&& pbd) {
const VkDevice device = mContext.device;
const VulkanRenderTarget* srcTarget = handle_cast<VulkanRenderTarget*>(src);
const VulkanTexture* srcTexture = srcTarget->getColor(mContext.currentSurface, 0).texture;
VulkanRenderTarget* srcTarget = handle_cast<VulkanRenderTarget*>(src);
VulkanTexture* srcTexture = srcTarget->getColor(mContext.currentSurface, 0).texture;
const VkFormat srcFormat = srcTexture ? srcTexture->getVkFormat() :
mContext.currentSurface->surfaceFormat.format;
const bool swizzle = srcFormat == VK_FORMAT_B8G8R8A8_UNORM;
@@ -1458,11 +1458,10 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
// TODO: staging should just use the GENERAL layout
transitionImageLayout(cmdbuffer, {
.image = stagingImage,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.subresources = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
@@ -1502,6 +1501,9 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
// Transition the source image layout (which might be the swap chain)
// Since ReadPixels is always issued after at least one render pass, we know that the color
// attachment layout is COLOR_ATTACHMENT_OPTIMAL.
const VkImageSubresourceRange srcRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = srcAttachment.level,
@@ -1510,11 +1512,10 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
.layerCount = 1,
};
// FIXME: the content of the source may be destroyed because of VK_IMAGE_LAYOUT_UNDEFINED
VkImage srcImage = srcTarget->getColor(mContext.currentSurface, 0).image;
VkImage srcImage = srcAttachment.image;
transitionImageLayout(cmdbuffer, {
.image = srcImage,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
.subresources = srcRange,
.srcStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
@@ -1523,33 +1524,22 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
});
// Perform the blit.
// Perform the into the staging area. At this point we know that the src layout is
// TRANSFER_SRC_OPTIMAL and the staging area is GENERAL.
vkCmdCopyImage(cmdbuffer, srcTarget->getColor(mContext.currentSurface, 0).image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, stagingImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, stagingImage, VK_IMAGE_LAYOUT_GENERAL,
1, &imageCopyRegion);
// Restore the source image layout.
// Restore the source image layout back to VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL.
if (srcTexture || mContext.currentSurface->presentQueue) {
const VkImageLayout present = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
// FIXME: the content of image we just blitted into may be destroyed because of VK_IMAGE_LAYOUT_UNDEFINED
transitionImageLayout(cmdbuffer, {
.image = srcImage,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = srcTexture ? mContext.getTextureLayout(srcTexture->usage) : present,
.subresources = srcRange,
.srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
});
if (UTILS_LIKELY(srcTexture)) {
srcTexture->transitionLayout(cmdbuffer, srcRange, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
} else {
// FIXME: the content of image we just blitted into may be destroyed because of VK_IMAGE_LAYOUT_UNDEFINED
transitionImageLayout(cmdbuffer, {
.image = srcImage,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.subresources = srcRange,
.srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
@@ -1558,30 +1548,6 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
});
}
// Transition the staging image layout to GENERAL.
// TODO: why is this not using transitionImageLayout() ?
VkImageMemoryBarrier barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = stagingImage,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
}
};
vkCmdPipelineBarrier(cmdbuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
// TODO: don't flush/wait here -- we should do this asynchronously
// Flush and wait.
@@ -1824,7 +1790,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
samplers[bindingPoint] = {
.sampler = vksampler,
.imageView = texture->getPrimaryImageView(),
.imageLayout = mContext.getTextureLayout(texture->usage)
.imageLayout = getDefaultImageLayout(texture->usage)
};
if (mContext.currentRenderPass.depthFeedback == texture) {

View File

@@ -138,16 +138,13 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
// In Vulkan, the subpass desc specifies the layout to transition to at the start of the render
// pass, and the attachment description specifies the layout to transition to at the end.
// However we use render passes to cause layout transitions only when drawing directly into the
// swap chain. We keep our offscreen images in GENERAL layout, which is simple and prevents
// thrashing the layout. Note that pipeline barriers are more powerful than render passes for
// performing layout transitions, because they allow for per-miplevel transitions.
// swap chain.
const bool discard = any(config.discardStart & TargetBufferFlags::COLOR);
struct { VkImageLayout subpass, initial, final; } colorLayouts[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT];
if (isSwapChain) {
colorLayouts[0].subpass = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
// It is legal to always use UNDEFINED for "initial", but we wish to avoid warnings
// when the load op is LOAD.
// Specifying UNDEFINED for "initial" can discard the existing data.
colorLayouts[0].initial = discard ? VK_IMAGE_LAYOUT_UNDEFINED :
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

View File

@@ -105,7 +105,7 @@ static VulkanAttachment createAttachment(VulkanContext& context, VulkanAttachmen
.view = {},
.memory = {},
.texture = spec.texture,
.layout = context.getTextureLayout(spec.texture->usage),
.layout = spec.texture->getVkLayout(spec.layer, spec.level),
.level = spec.level,
.layer = spec.layer
};

View File

@@ -289,6 +289,7 @@ void VulkanSwapChain::makePresentable() {
return;
}
VulkanAttachment& swapContext = color[currentSwapIndex];
assert_invariant(swapContext.layout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
VkImageMemoryBarrier barrier {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
@@ -311,7 +312,7 @@ void VulkanSwapChain::makePresentable() {
.oldLayout = firstRenderPass ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
#endif
.newLayout = swapContext.layout,
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = swapContext.image,

View File

@@ -38,6 +38,11 @@ VulkanTexture::VulkanTexture(VulkanContext& context, SamplerType target, uint8_t
mVkFormat(tformat == TextureFormat::DEPTH24 ? context.finalDepthFormat :
backend::getVkFormat(tformat)),
mAspect(any(usage & TextureUsage::DEPTH_ATTACHMENT) ? VK_IMAGE_ASPECT_DEPTH_BIT :
VK_IMAGE_ASPECT_COLOR_BIT),
mViewType(getImageViewType(target)),
mSwizzle(swizzle), mContext(context), mStagePool(stagePool) {
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
@@ -151,47 +156,33 @@ VulkanTexture::VulkanTexture(VulkanContext& context, SamplerType target, uint8_t
error = vkBindImageMemory(context.device, mTextureImage, mTextureImageMemory, 0);
ASSERT_POSTCONDITION(!error, "Unable to bind image.");
mAspect = any(usage & TextureUsage::DEPTH_ATTACHMENT) ? VK_IMAGE_ASPECT_DEPTH_BIT :
VK_IMAGE_ASPECT_COLOR_BIT;
// Spec out the "primary" VkImageView that shaders use to sample from the image.
mPrimaryViewRange.aspectMask = mAspect;
mPrimaryViewRange.baseMipLevel = 0;
mPrimaryViewRange.levelCount = levels;
mPrimaryViewRange.baseArrayLayer = 0;
if (target == SamplerType::SAMPLER_CUBEMAP) {
mViewType = VK_IMAGE_VIEW_TYPE_CUBE;
mPrimaryViewRange.layerCount = 6;
} else if (target == SamplerType::SAMPLER_2D_ARRAY) {
mViewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
mPrimaryViewRange.layerCount = depth;
} else if (target == SamplerType::SAMPLER_3D) {
mViewType = VK_IMAGE_VIEW_TYPE_3D;
mPrimaryViewRange.layerCount = 1;
} else {
mViewType = VK_IMAGE_VIEW_TYPE_2D;
mPrimaryViewRange.layerCount = 1;
}
// Go ahead and create the primary image view, no need to do it lazily.
getImageView(mPrimaryViewRange);
// Transition the layout of each image slice.
// TODO: The potentially redundant transition for SAMPLEABLE images.
if (any(usage & (TextureUsage::COLOR_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT | TextureUsage::SAMPLEABLE))) {
// Transition the layout of each image slice that might be used as a render target.
// We do not transition images that are merely SAMPLEABLE, this is deferred until upload time
// because we do not know how many layers and levels will actually be used.
if (any(usage & (TextureUsage::COLOR_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT))) {
const uint32_t layers = mPrimaryViewRange.layerCount;
transitionImageLayout(mContext.commands->get().cmdbuffer, textureTransitionHelper({
.image = mTextureImage,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = mContext.getTextureLayout(usage),
.subresources = {
mAspect,
0,
levels,
0,
layers
}
}));
VkImageSubresourceRange range = { mAspect, 0, levels, 0, layers };
VkImageLayout layout = getDefaultImageLayout(usage);
VkCommandBuffer commands = mContext.commands->get().cmdbuffer;
transitionLayout(commands, range, layout);
}
}
@@ -246,33 +237,14 @@ void VulkanTexture::updateWithCopyBuffer(const PixelBufferDescriptor& hostData,
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
// We can't blindly use LAYOUT_UNDEFINED because it may destroy the data, and because
// we're potentially updating only a sub-region it would be a problem.
VkImageLayout textureLayout = mContext.getTextureLayout(usage);
transitionImageLayout(cmdbuffer, textureTransitionHelper({
.image = mTextureImage,
.oldLayout = textureLayout,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.subresources = {
mAspect,
miplevel, 1,
0,1
}
}));
const VkImageSubresourceRange range = { mAspect, miplevel, 1, 0, 1 };
const VkImageLayout textureLayout = getDefaultImageLayout(usage);
transitionLayout(cmdbuffer, range, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
copyBufferToImage(cmdbuffer, stage->buffer, mTextureImage, width, height, depth,
nullptr, miplevel);
transitionImageLayout(cmdbuffer, textureTransitionHelper({
.image = mTextureImage,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = textureLayout,
.subresources = {
mAspect,
miplevel, 1,
0,1
}
}));
transitionLayout(cmdbuffer, range, textureLayout);
}
void VulkanTexture::updateWithBlitImage(const PixelBufferDescriptor& hostData, uint32_t width,
@@ -299,25 +271,14 @@ void VulkanTexture::updateWithBlitImage(const PixelBufferDescriptor& hostData, u
.dstOffsets = { rect[0], rect[1] }
}};
// We can't blindly use LAYOUT_UNDEFINED because it may destroy the data, and because
// we're potentially updating only a sub-region it would be a problem.
VkImageLayout textureLayout = mContext.getTextureLayout(usage);
transitionImageLayout(cmdbuffer, textureTransitionHelper({
.image = mTextureImage,
.oldLayout = textureLayout,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.subresources = { mAspect, miplevel, 1, 0, 1 }
}));
const VkImageSubresourceRange range = { mAspect, miplevel, 1, 0, 1 };
transitionLayout(cmdbuffer, range, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
vkCmdBlitImage(cmdbuffer, stage->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, mTextureImage,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, blitRegions, VK_FILTER_NEAREST);
transitionImageLayout(cmdbuffer, textureTransitionHelper({
.image = mTextureImage,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = textureLayout,
.subresources = { mAspect, miplevel, 1, 0, 1 }
}));
transitionLayout(cmdbuffer, range, getDefaultImageLayout(usage));
}
void VulkanTexture::updateCubeImage(const PixelBufferDescriptor& data,
@@ -340,29 +301,19 @@ void VulkanTexture::updateCubeImage(const PixelBufferDescriptor& data,
vmaUnmapMemory(mContext.allocator, stage->memory);
vmaFlushAllocation(mContext.allocator, stage->memory, 0, numDstBytes);
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
const uint32_t width = std::max(1u, this->width >> miplevel);
const uint32_t height = std::max(1u, this->height >> miplevel);
// We can use LAYOUT_UNDEFINED here because we're always replacing the whole data, so it
// doesn't matter if the previous data is lost.
transitionImageLayout(cmdbuffer, textureTransitionHelper({
.image = mTextureImage,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.subresources = { mAspect, miplevel, 1, 0, 6 }
}));
const VkImageSubresourceRange range = { mAspect, miplevel, 1, 0, 6 };
const VkImageLayout textureLayout = getDefaultImageLayout(usage);
transitionLayout(cmdbuffer, range, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
copyBufferToImage(cmdbuffer, stage->buffer, mTextureImage, width, height, 1,
&faceOffsets, miplevel);
transitionImageLayout(cmdbuffer, textureTransitionHelper({
.image = mTextureImage,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = mContext.getTextureLayout(usage),
.subresources = { mAspect, miplevel, 1, 0, 6 }
}));
transitionLayout(cmdbuffer, range, textureLayout);
}
void VulkanTexture::setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel) {
@@ -374,16 +325,13 @@ void VulkanTexture::setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel)
VkImageView VulkanTexture::getAttachmentView(int singleLevel, int singleLayer,
VkImageAspectFlags aspect) {
return getImageView({
VkImageSubresourceRange range = {
.aspectMask = aspect,
.baseMipLevel = uint32_t(singleLevel),
.levelCount = uint32_t(1),
.baseArrayLayer = uint32_t(singleLayer),
.layerCount = uint32_t(1),
}, true);
}
VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, bool isAttachment) {
};
auto iter = mCachedImageViews.find(range);
if (iter != mCachedImageViews.end()) {
return iter->second;
@@ -393,9 +341,30 @@ VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, bool isAt
.pNext = nullptr,
.flags = 0,
.image = mTextureImage,
.viewType = isAttachment ? VK_IMAGE_VIEW_TYPE_2D : mViewType,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = mVkFormat,
.components = isAttachment ? (VkComponentMapping{}) : mSwizzle,
.components = VkComponentMapping{},
.subresourceRange = range
};
VkImageView imageView;
vkCreateImageView(mContext.device, &viewInfo, VKALLOC, &imageView);
mCachedImageViews.emplace(range, imageView);
return imageView;
}
VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range) {
auto iter = mCachedImageViews.find(range);
if (iter != mCachedImageViews.end()) {
return iter->second;
}
VkImageViewCreateInfo viewInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.image = mTextureImage,
.viewType = mViewType,
.format = mVkFormat,
.components = mSwizzle,
.subresourceRange = range
};
VkImageView imageView;
@@ -435,5 +404,55 @@ void VulkanTexture::copyBufferToImage(VkCommandBuffer cmd, VkBuffer buffer, VkIm
vkCmdCopyBufferToImage(cmd, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
}
void VulkanTexture::transitionLayout(VkCommandBuffer commands, const VkImageSubresourceRange& range,
VkImageLayout newLayout) {
// In debug builds, ensure that all subresources in the given range have the same layout.
// It's easier to catch a mistake here than with validation, which waits until submission time.
VkImageLayout oldLayout = getVkLayout(range.baseArrayLayer, range.baseMipLevel);
#ifndef NDEBUG
if (oldLayout != VK_IMAGE_LAYOUT_UNDEFINED) {
for (uint32_t layer = 0; layer < range.layerCount; ++layer) {
for (uint32_t level = 0; level < range.levelCount; ++level) {
assert_invariant(getVkLayout(layer + range.baseArrayLayer,
level + range.baseMipLevel) == oldLayout);
}
}
}
#endif
transitionImageLayout(commands, textureTransitionHelper({
.image = mTextureImage,
.oldLayout = oldLayout,
.newLayout = newLayout,
.subresources = range,
}));
const uint32_t first_layer = range.baseArrayLayer;
const uint32_t last_layer = first_layer + range.layerCount;
const uint32_t first_level = range.baseMipLevel;
const uint32_t last_level = first_level + range.levelCount;
if (newLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
for (uint32_t layer = first_layer; layer < last_layer; ++layer) {
const uint32_t first = (layer << 16) | first_level;
const uint32_t last = (layer << 16) | last_level;
mSubresourceLayouts.clear(first, last);
}
} else {
for (uint32_t layer = first_layer; layer < last_layer; ++layer) {
const uint32_t first = (layer << 16) | first_level;
const uint32_t last = (layer << 16) | last_level;
mSubresourceLayouts.add(first, last, newLayout);
}
}
}
VkImageLayout VulkanTexture::getVkLayout(uint32_t layer, uint32_t level) const {
const uint32_t key = (layer << 16) | level;
if (!mSubresourceLayouts.has(key)) {
return VK_IMAGE_LAYOUT_UNDEFINED;
}
return mSubresourceLayouts.get(key);
}
} // namespace filament
} // namespace backend

View File

@@ -21,6 +21,8 @@
#include "VulkanBuffer.h"
#include "VulkanUtility.h"
#include <utils/RangeMap.h>
namespace filament {
namespace backend {
@@ -49,13 +51,17 @@ struct VulkanTexture : public HwTexture {
VkFormat getVkFormat() const { return mVkFormat; }
VkImage getVkImage() const { return mTextureImage; }
VkImageLayout getVkLayout(uint32_t layer, uint32_t level) const;
void setSidecar(VulkanTexture* sidecar) { mSidecarMSAA = sidecar; }
VulkanTexture* getSidecar() const { return mSidecarMSAA; }
void transitionLayout(VkCommandBuffer commands, const VkImageSubresourceRange& range,
VkImageLayout newLayout);
private:
// Gets or creates a cached VkImageView for a range of miplevels and array layers.
// If isAttachment is true, this always returns a 2D image view without swizzle.
VkImageView getImageView(VkImageSubresourceRange range, bool isAttachment = false);
VkImageView getImageView(VkImageSubresourceRange range);
// Issues a copy from a VkBuffer to a specified miplevel in a VkImage. The given width and
// height define a subregion within the miplevel.
@@ -71,13 +77,20 @@ private:
VulkanTexture* mSidecarMSAA = nullptr;
const VkFormat mVkFormat;
const VkImageAspectFlags mAspect;
const VkImageViewType mViewType;
const VkComponentMapping mSwizzle;
VkImageViewType mViewType;
VkImage mTextureImage = VK_NULL_HANDLE;
VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE;
// Track the image layout of each subresource using a sparse range map.
utils::RangeMap<uint32_t, VkImageLayout> mSubresourceLayouts;
// Track the range of subresources that define the "primary" image view, which is the special
// image view that gets bound to an actual texture sampler.
VkImageSubresourceRange mPrimaryViewRange;
std::map<VkImageSubresourceRange, VkImageView> mCachedImageViews;
VkImageAspectFlags mAspect;
VulkanContext& mContext;
VulkanStagePool& mStagePool;
};

View File

@@ -501,6 +501,38 @@ VkComponentMapping getSwizzleMap(TextureSwizzle swizzle[4]) {
return map;
}
VkImageViewType getImageViewType(SamplerType target) {
switch (target) {
case SamplerType::SAMPLER_CUBEMAP:
return VK_IMAGE_VIEW_TYPE_CUBE;
case SamplerType::SAMPLER_2D_ARRAY:
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
case SamplerType::SAMPLER_3D:
return VK_IMAGE_VIEW_TYPE_3D;
default:
return VK_IMAGE_VIEW_TYPE_2D;
}
}
VkImageLayout getDefaultImageLayout(TextureUsage usage) {
// Filament sometimes samples from depth while it is bound to the current render target, (e.g.
// SSAO does this while depth writes are disabled) so let's keep it simple and use GENERAL for
// all depth textures.
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
return VK_IMAGE_LAYOUT_GENERAL;
}
// Filament sometimes samples from one miplevel while writing to another level in the same
// texture (e.g. bloom does this). Moreover we'd like to avoid lots of expensive layout
// transitions. So, keep it simple and use GENERAL for all color-attachable textures.
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
return VK_IMAGE_LAYOUT_GENERAL;
}
// Finally, the layout for an immutable texture is optimal read-only.
return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
void transitionImageLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition) {
if (transition.oldLayout == transition.newLayout) {
return;
@@ -564,7 +596,7 @@ VulkanLayoutTransition textureTransitionHelper(VulkanLayoutTransition transition
break;
// We support PRESENT as a target layout to allow blitting from the swap chain.
// See also makeSwapChainPresentable().
// See also SwapChain::makePresentable().
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
transition.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;

View File

@@ -47,6 +47,9 @@ VkCullModeFlags getCullMode(CullingMode mode);
VkFrontFace getFrontFace(bool inverseFrontFaces);
PixelDataType getComponentType(VkFormat format);
VkComponentMapping getSwizzleMap(TextureSwizzle swizzle[4]);
VkImageViewType getImageViewType(SamplerType target);
VkImageLayout getDefaultImageLayout(TextureUsage usage);
void transitionImageLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition);
// Helper function for populating barrier fields based on the desired image layout.