Skip to content

Commit 4ee281f

Browse files
authored
fixes the vulkan image layout transitions for mipmap generation (flutter#173884)
Speculative fix for flutter#171252 The image layout transitions were wrong when generating mipmaps where we'd run into `width <= 1 || height <= 1`. Previously the logic would short circuit the process of converting everything from Dst->Src, potentially leaving the mips in a series like [Read, Read, Src, Dst]. Then only the last one would get set to read, so [Read, Read, Src, Read]. Now instead we group transition them all to Dst, then one by one we switch them to src, make sure the last one gets set to src, then group transition them to read. We make sure the loop doesn't break to make sure everything transitions to src. While this fixes a hypothetical bug, I couldn't replicate the bug inside of unit tests because of validations. I couldn't find a mip count / texture size that would allow me to hit the `break` statement that jonah added previously. That doesn't mean it couldn't happen on release builds though. I'm less confident that this fixes the issue after having written the tests. This does reduce the number of barriers though, so that's a win. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
1 parent b649c19 commit 4ee281f

6 files changed

Lines changed: 114 additions & 15 deletions

File tree

engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ impeller_component("vulkan_unittests") {
1010
testonly = true
1111
sources = [
1212
"allocator_vk_unittests.cc",
13+
"blit_pass_vk_unittests.cc",
1314
"command_encoder_vk_unittests.cc",
1415
"command_pool_vk_unittests.cc",
1516
"context_vk_unittests.cc",

engine/src/flutter/impeller/renderer/backend/vulkan/blit_pass_vk.cc

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,8 @@ bool BlitPassVK::OnGenerateMipmapCommand(std::shared_ptr<Texture> texture,
479479
width = width / 2;
480480
height = height / 2;
481481
if (width <= 1 || height <= 1) {
482-
break;
482+
// Continue to make sure everything is placed into eTransferSrcOptimal.
483+
continue;
483484
}
484485

485486
// offsets[0] is origin.
@@ -495,24 +496,27 @@ bool BlitPassVK::OnGenerateMipmapCommand(std::shared_ptr<Texture> texture,
495496
&blit, // regions
496497
vk::Filter::eLinear // filter
497498
);
498-
499-
barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal;
500-
barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
501-
barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead;
502-
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
503-
504-
// Now that the blit is done, the image at the previous level (N-1)
505-
// is done reading from (TransferSrc)/ Now we must prepare it to be read
506-
// from a shader (ShaderReadOnly).
507-
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
508-
vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {},
509-
{barrier});
510499
}
511500

501+
// Switch the last one to eTransferSrcOptimal.
512502
barrier.subresourceRange.baseMipLevel = mip_count - 1;
503+
barrier.subresourceRange.levelCount = 1;
513504
barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
514-
barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
505+
barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal;
515506
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
507+
barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead;
508+
509+
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
510+
vk::PipelineStageFlagBits::eTransfer, {}, {}, {},
511+
{barrier});
512+
513+
// Now everything is in eTransferSrcOptimal, switch everything to
514+
// eShaderReadOnlyOptimal.
515+
barrier.subresourceRange.baseMipLevel = 0;
516+
barrier.subresourceRange.levelCount = mip_count;
517+
barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal;
518+
barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
519+
barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead;
516520
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
517521

518522
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,

engine/src/flutter/impeller/renderer/backend/vulkan/blit_pass_vk.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
#ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_BLIT_PASS_VK_H_
66
#define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_BLIT_PASS_VK_H_
77

8-
#include "flutter/impeller/base/config.h"
8+
#include "flutter/fml/macros.h"
9+
#include "impeller/base/config.h"
910
#include "impeller/geometry/rect.h"
1011
#include "impeller/renderer/backend/vulkan/workarounds_vk.h"
1112
#include "impeller/renderer/blit_pass.h"
@@ -22,6 +23,8 @@ class BlitPassVK final : public BlitPass {
2223

2324
private:
2425
friend class CommandBufferVK;
26+
FML_FRIEND_TEST(BlitPassVKTest,
27+
MipmapGenerationTransitionsAllLevelsCorrectly);
2528

2629
std::shared_ptr<CommandBufferVK> command_buffer_;
2730
const WorkaroundsVK workarounds_;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/testing/testing.h"
6+
#include "fml/macros.h"
7+
#include "impeller/renderer/backend/vulkan/blit_pass_vk.h"
8+
#include "impeller/renderer/backend/vulkan/command_buffer_vk.h"
9+
#include "impeller/renderer/backend/vulkan/test/mock_vulkan.h"
10+
11+
namespace impeller {
12+
13+
TEST(BlitPassVKTest, MipmapGenerationTransitionsAllLevelsCorrectly) {
14+
auto context = testing::MockVulkanContextBuilder().Build();
15+
ASSERT_TRUE(context->IsValid());
16+
17+
auto cmd_buffer = context->CreateCommandBuffer();
18+
ASSERT_TRUE(cmd_buffer);
19+
auto blit_pass = cmd_buffer->CreateBlitPass();
20+
ASSERT_TRUE(blit_pass);
21+
22+
auto vk_blit_pass = reinterpret_cast<BlitPassVK*>(blit_pass.get());
23+
auto vk_cmd_buffer = reinterpret_cast<CommandBufferVK*>(cmd_buffer.get());
24+
25+
TextureDescriptor desc;
26+
desc.size = ISize(100, 65);
27+
desc.format = PixelFormat::kR8G8B8A8UNormInt;
28+
desc.mip_count = 6;
29+
auto texture = context->GetResourceAllocator()->CreateTexture(desc);
30+
ASSERT_TRUE(texture);
31+
32+
ASSERT_TRUE(vk_blit_pass->OnGenerateMipmapCommand(texture, "TestMipmap"));
33+
34+
auto& barriers =
35+
testing::GetImageMemoryBarriers(vk_cmd_buffer->GetCommandBuffer());
36+
37+
ASSERT_EQ(barriers.size(), 8u);
38+
39+
EXPECT_EQ(barriers[0].oldLayout, VK_IMAGE_LAYOUT_UNDEFINED);
40+
EXPECT_EQ(barriers[0].newLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
41+
EXPECT_EQ(barriers[0].subresourceRange.baseMipLevel, 0u);
42+
EXPECT_EQ(barriers[0].subresourceRange.levelCount, 6u);
43+
44+
for (uint32_t i = 1; i < 7; ++i) {
45+
EXPECT_EQ(barriers[i].oldLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) << i;
46+
EXPECT_EQ(barriers[i].newLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) << i;
47+
EXPECT_EQ(barriers[i].subresourceRange.baseMipLevel, i - 1) << i;
48+
EXPECT_EQ(barriers[i].subresourceRange.levelCount, 1u) << i;
49+
}
50+
51+
EXPECT_EQ(barriers[7].oldLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
52+
EXPECT_EQ(barriers[7].newLayout, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
53+
EXPECT_EQ(barriers[7].subresourceRange.baseMipLevel, 0u);
54+
EXPECT_EQ(barriers[7].subresourceRange.levelCount, 6u);
55+
}
56+
57+
} // namespace impeller

engine/src/flutter/impeller/renderer/backend/vulkan/test/mock_vulkan.cc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ struct MockCommandBuffer {
2525
std::shared_ptr<std::vector<std::string>> called_functions)
2626
: called_functions_(std::move(called_functions)) {}
2727
std::shared_ptr<std::vector<std::string>> called_functions_;
28+
std::vector<VkImageMemoryBarrier> image_memory_barriers_;
2829
};
2930

3031
struct MockQueryPool {};
@@ -448,6 +449,27 @@ void vkCmdBindPipeline(VkCommandBuffer commandBuffer,
448449
mock_command_buffer->called_functions_->push_back("vkCmdBindPipeline");
449450
}
450451

452+
void vkCmdPipelineBarrier(VkCommandBuffer commandBuffer,
453+
VkPipelineStageFlags srcStageMask,
454+
VkPipelineStageFlags dstStageMask,
455+
VkDependencyFlags dependencyFlags,
456+
uint32_t memoryBarrierCount,
457+
const VkMemoryBarrier* pMemoryBarriers,
458+
uint32_t bufferMemoryBarrierCount,
459+
const VkBufferMemoryBarrier* pBufferMemoryBarriers,
460+
uint32_t imageMemoryBarrierCount,
461+
const VkImageMemoryBarrier* pImageMemoryBarriers) {
462+
MockCommandBuffer* mock_command_buffer =
463+
reinterpret_cast<MockCommandBuffer*>(commandBuffer);
464+
mock_command_buffer->called_functions_->push_back("vkCmdPipelineBarrier");
465+
if (pImageMemoryBarriers) {
466+
for (uint32_t i = 0; i < imageMemoryBarrierCount; ++i) {
467+
mock_command_buffer->image_memory_barriers_.push_back(
468+
pImageMemoryBarriers[i]);
469+
}
470+
}
471+
}
472+
451473
void vkCmdSetStencilReference(VkCommandBuffer commandBuffer,
452474
VkStencilFaceFlags faceMask,
453475
uint32_t reference) {
@@ -863,6 +885,8 @@ PFN_vkVoidFunction GetMockVulkanProcAddress(VkInstance instance,
863885
return reinterpret_cast<PFN_vkVoidFunction>(vkDestroyPipelineCache);
864886
} else if (strcmp("vkCmdBindPipeline", pName) == 0) {
865887
return reinterpret_cast<PFN_vkVoidFunction>(vkCmdBindPipeline);
888+
} else if (strcmp("vkCmdPipelineBarrier", pName) == 0) {
889+
return reinterpret_cast<PFN_vkVoidFunction>(vkCmdPipelineBarrier);
866890
} else if (strcmp("vkCmdSetStencilReference", pName) == 0) {
867891
return reinterpret_cast<PFN_vkVoidFunction>(vkCmdSetStencilReference);
868892
} else if (strcmp("vkCmdSetScissor", pName) == 0) {
@@ -988,5 +1012,12 @@ void SetSwapchainImageSize(ISize size) {
9881012
currentImageSize = size;
9891013
}
9901014

1015+
std::vector<VkImageMemoryBarrier>& GetImageMemoryBarriers(
1016+
VkCommandBuffer buffer) {
1017+
MockCommandBuffer* mock_command_buffer =
1018+
reinterpret_cast<MockCommandBuffer*>(buffer);
1019+
return mock_command_buffer->image_memory_barriers_;
1020+
}
1021+
9911022
} // namespace testing
9921023
} // namespace impeller

engine/src/flutter/impeller/renderer/backend/vulkan/test/mock_vulkan.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ class MockVulkanContextBuilder {
131131
/// @brief Override the image size returned by all swapchain images.
132132
void SetSwapchainImageSize(ISize size);
133133

134+
std::vector<VkImageMemoryBarrier>& GetImageMemoryBarriers(
135+
VkCommandBuffer buffer);
136+
134137
} // namespace testing
135138
} // namespace impeller
136139

0 commit comments

Comments
 (0)