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:
committed by
Mathias Agopian
parent
2da215e8e7
commit
c0ba260ddf
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user