blob: d1c7d006377622d5463d654d210713868c81620d [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/ui/lib/escher/vk/impl/render_pass_cache.h"
#include "src/ui/lib/escher/impl/vulkan_utils.h"
#include "src/ui/lib/escher/resources/resource_recycler.h"
#include "src/ui/lib/escher/test/common/gtest_escher.h"
#include "src/ui/lib/escher/third_party/granite/vk/render_pass.h"
#include "src/ui/lib/escher/vk/render_pass_info.h"
#include "src/ui/lib/escher/vk/texture.h"
namespace {
using namespace escher;
void CompareRenderPassWithInfo(const impl::RenderPassPtr& render_pass, const RenderPassInfo& info) {
EXPECT_EQ(render_pass->num_color_attachments(), info.num_color_attachments);
const uint32_t num_subpasses = render_pass->num_subpasses();
if (info.subpasses.size() == 0) {
// Vulkan requires at least one subpass per render pass, so if none is
// provided, then a default is created.
ASSERT_EQ(num_subpasses, 1U);
EXPECT_EQ(render_pass->GetInputAttachmentCountForSubpass(0), 0U);
EXPECT_EQ(render_pass->GetColorAttachmentCountForSubpass(0), info.num_color_attachments);
} else {
// Subpasses are explicitly specified in the RenderPassInfo.
ASSERT_EQ(num_subpasses, info.subpasses.size());
for (uint32_t i = 0; i < num_subpasses; ++i) {
EXPECT_EQ(render_pass->GetColorAttachmentCountForSubpass(i),
info.subpasses[i].num_color_attachments);
EXPECT_EQ(render_pass->GetInputAttachmentCountForSubpass(i),
info.subpasses[i].num_input_attachments);
}
}
}
using RenderPassCacheTest = test::TestWithVkValidationLayer;
VK_TEST_F(RenderPassCacheTest, DefaultSubpass) {
auto escher = test::GetEscher();
impl::RenderPassCache cache(escher->resource_recycler());
EXPECT_EQ(cache.size(), 0U);
uint32_t width = 1024;
uint32_t height = 1024;
// Get depth stencil texture format supported by the device.
std::set<vk::Format> supported_depth_stencil_formats_set =
escher->device()->caps().GetAllMatchingDepthStencilFormats(
{vk::Format::eD16UnormS8Uint, vk::Format::eD24UnormS8Uint, vk::Format::eD32SfloatS8Uint});
std::vector<vk::Format> supported_depth_stencil_formats = std::vector(
supported_depth_stencil_formats_set.begin(), supported_depth_stencil_formats_set.end());
if (supported_depth_stencil_formats.empty()) {
FX_LOGS(ERROR) << "No depth stencil format is supported on this device, test terminated.";
EXPECT_TRUE(escher->Cleanup());
return;
}
TexturePtr depth_tex1 = escher->NewAttachmentTexture(supported_depth_stencil_formats[0], width,
height, 1, vk::Filter::eNearest);
TexturePtr depth_tex2 = escher->NewAttachmentTexture(supported_depth_stencil_formats[0], width,
height, 1, vk::Filter::eNearest);
TexturePtr color_tex = escher->NewAttachmentTexture(vk::Format::eB8G8R8A8Unorm, width, height, 1,
vk::Filter::eNearest);
RenderPassInfo info;
info.op_flags = RenderPassInfo::kOptimalColorLayoutOp;
info.num_color_attachments = 1;
info.color_attachments[0] = color_tex;
info.depth_stencil_attachment = depth_tex1;
RenderPassInfo::InitRenderPassAttachmentInfosFromImages(&info);
CompareRenderPassWithInfo(cache.ObtainRenderPass(info, true), info);
EXPECT_EQ(cache.size(), 1U);
// The same RenderPass should be obtained if a different image is provided,
// but with the same format.
info.depth_stencil_attachment = depth_tex2;
RenderPassInfo::InitRenderPassAttachmentInfosFromImages(&info);
CompareRenderPassWithInfo(cache.ObtainRenderPass(info, true), info);
EXPECT_EQ(cache.size(), 1U);
// Using a different image format should result in a different RenderPass.
// However we cannot test it if there is only one image format supported.
if (supported_depth_stencil_formats.size() == 1) {
FX_LOGS(ERROR) << "Only one depth stencil format is supported on this device, test terminated.";
} else {
depth_tex1 = escher->NewAttachmentTexture(supported_depth_stencil_formats[1], width, height, 1,
vk::Filter::eNearest);
info.depth_stencil_attachment = depth_tex1;
RenderPassInfo::InitRenderPassAttachmentInfosFromImages(&info);
CompareRenderPassWithInfo(cache.ObtainRenderPass(info, true), info);
EXPECT_EQ(cache.size(), 2U);
depth_tex1 = depth_tex2 = color_tex = nullptr;
}
EXPECT_TRUE(escher->Cleanup());
}
// Helper function for RenderPassCache.RespectsSampleCount.
static void InitRenderPassInfo(RenderPassInfo* rp, const TexturePtr& depth_tex,
const TexturePtr& color_tex, const TexturePtr& resolve_tex) {
const uint32_t width = depth_tex->width();
const uint32_t height = depth_tex->height();
const uint32_t sample_count = depth_tex->image()->info().sample_count;
FX_DCHECK(width == color_tex->width());
FX_DCHECK(height == color_tex->height());
FX_DCHECK(sample_count == color_tex->image()->info().sample_count);
static constexpr uint32_t kRenderTargetAttachmentIndex = 0;
static constexpr uint32_t kResolveTargetAttachmentIndex = 1;
rp->depth_stencil_attachment = depth_tex;
rp->color_attachments[kRenderTargetAttachmentIndex] = color_tex;
rp->num_color_attachments = 1;
// Clear and store color attachment 0, the sole color attachment.
rp->clear_attachments = 1u << kRenderTargetAttachmentIndex;
rp->store_attachments = 1u << kRenderTargetAttachmentIndex;
// Standard flags for a depth-testing render-pass that needs to first clear
// the depth image.
rp->op_flags = RenderPassInfo::kClearDepthStencilOp | RenderPassInfo::kOptimalColorLayoutOp |
RenderPassInfo::kOptimalDepthStencilLayoutOp;
rp->clear_color[0].setFloat32({0.f, 0.f, 0.f, 0.f});
if (sample_count == 1) {
FX_DCHECK(!resolve_tex);
rp->color_attachments[kRenderTargetAttachmentIndex] = color_tex;
rp->subpasses.push_back(RenderPassInfo::Subpass{
.color_attachments = {kRenderTargetAttachmentIndex},
.input_attachments = {},
.resolve_attachments = {},
.num_color_attachments = 1,
.num_input_attachments = 0,
.num_resolve_attachments = 0,
});
} else {
FX_DCHECK(resolve_tex);
FX_DCHECK(resolve_tex->image()->info().sample_count == 1);
FX_DCHECK(width == resolve_tex->width());
FX_DCHECK(height == resolve_tex->height());
rp->color_attachments[kResolveTargetAttachmentIndex] = resolve_tex;
rp->num_color_attachments++;
rp->subpasses.push_back(RenderPassInfo::Subpass{
.color_attachments = {kRenderTargetAttachmentIndex},
.input_attachments = {},
.resolve_attachments = {kResolveTargetAttachmentIndex},
.num_color_attachments = 1,
.num_input_attachments = 0,
.num_resolve_attachments = 1,
});
}
RenderPassInfo::InitRenderPassAttachmentInfosFromImages(rp);
}
VK_TEST_F(RenderPassCacheTest, RespectsSampleCount) {
auto escher = test::GetEscher();
impl::RenderPassCache cache(escher->resource_recycler());
EXPECT_EQ(cache.size(), 0U);
uint32_t width = 1024;
uint32_t height = 1024;
// Get depth stencil texture format supported by the device.
auto depth_stencil_texture_format_result =
escher->device()->caps().GetMatchingDepthStencilFormat();
if (depth_stencil_texture_format_result.result != vk::Result::eSuccess) {
FX_LOGS(ERROR) << "No depth stencil format is supported on this device.";
return;
}
// Attachments and renderpass info for no MSAA.
TexturePtr depth_tex1 = escher->NewAttachmentTexture(depth_stencil_texture_format_result.value,
width, height, 1, vk::Filter::eNearest);
TexturePtr color_tex1a = escher->NewAttachmentTexture(vk::Format::eB8G8R8A8Unorm, width, height,
1, vk::Filter::eNearest);
TexturePtr color_tex1b = escher->NewAttachmentTexture(vk::Format::eB8G8R8A8Unorm, width, height,
1, vk::Filter::eNearest);
RenderPassInfo info1a, info1b;
bool sample_1_supported = false;
if (depth_tex1 || (color_tex1a && color_tex1b)) {
InitRenderPassInfo(&info1a, depth_tex1, color_tex1a, nullptr);
InitRenderPassInfo(&info1b, depth_tex1, color_tex1b, nullptr);
sample_1_supported = true;
}
// Attachments and renderpass info for 2x MSAA.
TexturePtr depth_tex2 = escher->NewAttachmentTexture(depth_stencil_texture_format_result.value,
width, height, 2, vk::Filter::eNearest);
TexturePtr color_tex2a = escher->NewAttachmentTexture(vk::Format::eB8G8R8A8Unorm, width, height,
2, vk::Filter::eNearest);
TexturePtr color_tex2b = escher->NewAttachmentTexture(vk::Format::eB8G8R8A8Unorm, width, height,
2, vk::Filter::eNearest);
RenderPassInfo info2a, info2b;
bool sample_2_supported = false;
if (depth_tex2 || (color_tex2a && color_tex2b)) {
InitRenderPassInfo(&info2a, depth_tex2, color_tex2a, color_tex1a);
InitRenderPassInfo(&info2b, depth_tex2, color_tex2b, color_tex1b);
sample_2_supported = true;
}
// Attachments and renderpass info for 4x MSAA.
TexturePtr depth_tex4 = escher->NewAttachmentTexture(depth_stencil_texture_format_result.value,
width, height, 4, vk::Filter::eNearest);
TexturePtr color_tex4a = escher->NewAttachmentTexture(vk::Format::eB8G8R8A8Unorm, width, height,
4, vk::Filter::eNearest);
TexturePtr color_tex4b = escher->NewAttachmentTexture(vk::Format::eB8G8R8A8Unorm, width, height,
4, vk::Filter::eNearest);
RenderPassInfo info4a, info4b;
bool sample_4_supported = false;
if (depth_tex4 || (color_tex4a && color_tex4b)) {
InitRenderPassInfo(&info4a, depth_tex4, color_tex4a, color_tex1a);
InitRenderPassInfo(&info4b, depth_tex4, color_tex4b, color_tex1b);
sample_4_supported = true;
}
impl::RenderPassPtr rp1a, rp1b, rp2a, rp2b, rp4a, rp4b;
if (sample_1_supported) {
rp1a = cache.ObtainRenderPass(info1a, true);
rp1b = cache.ObtainRenderPass(info1b, true);
}
if (sample_2_supported) {
rp2a = cache.ObtainRenderPass(info2a, true);
rp2b = cache.ObtainRenderPass(info2b, true);
}
if (sample_4_supported) {
rp4a = cache.ObtainRenderPass(info4a, true);
rp4b = cache.ObtainRenderPass(info4b, true);
}
// Same cached renderpass should be returned for info with the same sample count (but different
// framebuffer images).
// We ignore the result if a sample count is not supported.
EXPECT_TRUE(!sample_1_supported || rp1a == rp1b);
EXPECT_TRUE(!sample_2_supported || rp2a == rp2b);
EXPECT_TRUE(!sample_4_supported || rp4a == rp4b);
// Different cached renderpass should be returned when the sample count differs.
// We ignore the result if a sample count is not supported.
EXPECT_TRUE(!sample_1_supported || !sample_2_supported || rp1a != rp2a);
EXPECT_TRUE(!sample_1_supported || !sample_4_supported || rp1a != rp4a);
EXPECT_TRUE(!sample_2_supported || !sample_4_supported || rp2a != rp4a);
EXPECT_TRUE(escher->Cleanup());
}
VK_TEST_F(RenderPassCacheTest, UnexpectedLazyCreationCallback) {
auto escher = test::GetEscher();
impl::RenderPassCache cache(escher->resource_recycler());
// Create two incompatible RenderPassInfos, differing only in the format of the output image.
RenderPassInfo rpi1, rpi2;
{
const vk::Format depth_stencil_format =
ESCHER_CHECKED_VK_RESULT(escher->device()->caps().GetMatchingDepthStencilFormat(
{vk::Format::eD24UnormS8Uint, vk::Format::eD32SfloatS8Uint}));
RenderPassInfo::AttachmentInfo color_attachment_info{
.format = vk::Format::eB8G8R8A8Unorm,
.swapchain_layout = vk::ImageLayout::eColorAttachmentOptimal,
.sample_count = 1,
.is_transient = false,
};
RenderPassInfo::InitRenderPassInfo(&rpi1, color_attachment_info, depth_stencil_format,
vk::Format::eUndefined, 1, false);
color_attachment_info.format = vk::Format::eR8G8B8A8Unorm;
RenderPassInfo::InitRenderPassInfo(&rpi2, color_attachment_info, depth_stencil_format,
vk::Format::eUndefined, 1, false);
}
FX_LOGS(INFO) << "============= NOTE: Escher warnings expected";
// No callback has been set and |allow_render_pass_creation| is false. Default behavior is to
// return nullptr.
EXPECT_FALSE(cache.ObtainRenderPass(rpi1, false));
EXPECT_EQ(0U, cache.size());
bool callback_was_called = false;
bool callback_should_allow_lazy_creation = false;
cache.set_unexpected_lazy_creation_callback([&](const RenderPassInfo& rpi) {
callback_was_called = true;
return callback_should_allow_lazy_creation;
});
// The callback will answer false, so no render-pass will be created/obtained.
EXPECT_FALSE(cache.ObtainRenderPass(rpi1, false));
EXPECT_TRUE(callback_was_called);
EXPECT_EQ(0U, cache.size());
// Same thing again, same result.
callback_was_called = false;
EXPECT_FALSE(cache.ObtainRenderPass(rpi1, false));
EXPECT_TRUE(callback_was_called);
EXPECT_EQ(0U, cache.size());
// Switch over to the second render-pass, rpi2.
callback_was_called = false;
EXPECT_FALSE(cache.ObtainRenderPass(rpi2, false));
EXPECT_TRUE(callback_was_called);
// The rest of the test will either allow lazy creation (whether via the argument to
// ObtainRenderPass(), or via the closure), or the render-pass will already be cached. Therefore,
// we expect no warnings after this point.
FX_LOGS(INFO) << "============= NOTE: no additional Escher warnings are expected\n";
// Switch back to the first render-pass, rpi1.
// This time the callback will answer true, so a render-pass will be created/obtained, even
// though |allow_render_pass_creation| is false.
EXPECT_EQ(0U, cache.size());
callback_was_called = false;
callback_should_allow_lazy_creation = true;
EXPECT_TRUE(cache.ObtainRenderPass(rpi1, false));
EXPECT_EQ(1U, cache.size());
EXPECT_TRUE(callback_was_called);
callback_was_called = false;
// Because the render-pass already exists, the callback won't be invoked.
EXPECT_TRUE(cache.ObtainRenderPass(rpi1, false));
EXPECT_FALSE(callback_was_called);
// Changing |allow_render_pass_creation| doesn't matter; it will return the identical render-pass.
EXPECT_EQ(cache.ObtainRenderPass(rpi1, false), cache.ObtainRenderPass(rpi1, true));
// Switch back to rpi2 for the last time. This time we allow lazy creation via
// |allow_render_pass| instead of the closure; there is no need to call the callback because lazy
// creation is explicitly allowed by the caller.
EXPECT_EQ(1U, cache.size());
callback_was_called = false;
callback_should_allow_lazy_creation = false;
EXPECT_TRUE(cache.ObtainRenderPass(rpi2, true));
EXPECT_FALSE(callback_was_called);
EXPECT_EQ(2U, cache.size());
// The render-pass is now cached, so will be found if we change |allow_render_pass| back to false;
// the callback is still not called.
EXPECT_TRUE(cache.ObtainRenderPass(rpi2, false));
EXPECT_FALSE(callback_was_called);
// As before, verify that we can obtain the same render-pass two ways.
EXPECT_EQ(cache.ObtainRenderPass(rpi2, false), cache.ObtainRenderPass(rpi2, true));
// Finally, verify that rpi1 and rpi2 return different render-passes.
EXPECT_NE(cache.ObtainRenderPass(rpi1, true), cache.ObtainRenderPass(rpi2, true));
}
} // anonymous namespace