diff --git a/filament/backend/src/vulkan/VulkanBlitter.cpp b/filament/backend/src/vulkan/VulkanBlitter.cpp index 2ec2c29099..248168937b 100644 --- a/filament/backend/src/vulkan/VulkanBlitter.cpp +++ b/filament/backend/src/vulkan/VulkanBlitter.cpp @@ -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, diff --git a/filament/backend/src/vulkan/VulkanContext.cpp b/filament/backend/src/vulkan/VulkanContext.cpp index 9126675b15..296a4a79f0 100644 --- a/filament/backend/src/vulkan/VulkanContext.cpp +++ b/filament/backend/src/vulkan/VulkanContext.cpp @@ -333,25 +333,6 @@ VkFormat VulkanContext::findSupportedFormat(utils::Slice 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, diff --git a/filament/backend/src/vulkan/VulkanContext.h b/filament/backend/src/vulkan/VulkanContext.h index 526d6f8b99..73030d326b 100644 --- a/filament/backend/src/vulkan/VulkanContext.h +++ b/filament/backend/src/vulkan/VulkanContext.h @@ -72,7 +72,6 @@ struct VulkanContext { uint32_t selectMemoryType(uint32_t flags, VkFlags reqs); VkFormat findSupportedFormat(utils::Slice candidates, VkImageTiling tiling, VkFormatFeatureFlags features); - VkImageLayout getTextureLayout(TextureUsage usage) const; void createEmptyTexture(VulkanStagePool& stagePool); VkInstance instance; diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 4a7cd18805..11b6aa6737 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -1412,8 +1412,8 @@ void VulkanDriver::stopCapture(int) { void VulkanDriver::readPixels(Handle 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(src); - const VulkanTexture* srcTexture = srcTarget->getColor(mContext.currentSurface, 0).texture; + VulkanRenderTarget* srcTarget = handle_cast(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 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 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 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 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 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 r samplers[bindingPoint] = { .sampler = vksampler, .imageView = texture->getPrimaryImageView(), - .imageLayout = mContext.getTextureLayout(texture->usage) + .imageLayout = getDefaultImageLayout(texture->usage) }; if (mContext.currentRenderPass.depthFeedback == texture) { diff --git a/filament/backend/src/vulkan/VulkanFboCache.cpp b/filament/backend/src/vulkan/VulkanFboCache.cpp index 85f0679628..e31447df58 100644 --- a/filament/backend/src/vulkan/VulkanFboCache.cpp +++ b/filament/backend/src/vulkan/VulkanFboCache.cpp @@ -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; diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index 1cd45ee92c..eb3e359d66 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -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 }; diff --git a/filament/backend/src/vulkan/VulkanSwapChain.cpp b/filament/backend/src/vulkan/VulkanSwapChain.cpp index 2db43d420b..59d8f4a5b3 100644 --- a/filament/backend/src/vulkan/VulkanSwapChain.cpp +++ b/filament/backend/src/vulkan/VulkanSwapChain.cpp @@ -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, diff --git a/filament/backend/src/vulkan/VulkanTexture.cpp b/filament/backend/src/vulkan/VulkanTexture.cpp index 2bc0f203b5..41537481c8 100644 --- a/filament/backend/src/vulkan/VulkanTexture.cpp +++ b/filament/backend/src/vulkan/VulkanTexture.cpp @@ -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, ®ion); } +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 diff --git a/filament/backend/src/vulkan/VulkanTexture.h b/filament/backend/src/vulkan/VulkanTexture.h index 2d3cd6a207..29ca732d18 100644 --- a/filament/backend/src/vulkan/VulkanTexture.h +++ b/filament/backend/src/vulkan/VulkanTexture.h @@ -21,6 +21,8 @@ #include "VulkanBuffer.h" #include "VulkanUtility.h" +#include + 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 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 mCachedImageViews; - VkImageAspectFlags mAspect; VulkanContext& mContext; VulkanStagePool& mStagePool; }; diff --git a/filament/backend/src/vulkan/VulkanUtility.cpp b/filament/backend/src/vulkan/VulkanUtility.cpp index f32261720d..0fe023be99 100644 --- a/filament/backend/src/vulkan/VulkanUtility.cpp +++ b/filament/backend/src/vulkan/VulkanUtility.cpp @@ -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; diff --git a/filament/backend/src/vulkan/VulkanUtility.h b/filament/backend/src/vulkan/VulkanUtility.h index c2ebc58f22..270f39e8cb 100644 --- a/filament/backend/src/vulkan/VulkanUtility.h +++ b/filament/backend/src/vulkan/VulkanUtility.h @@ -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.