blob: faf2d5a8d68cae8bb5b2e0a45243cc992cb55e29 [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/framebuffer_allocator.h"
#include <vector>
#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/impl/framebuffer.h"
#include "src/ui/lib/escher/vk/impl/render_pass_cache.h"
#include "src/ui/lib/escher/vk/render_pass_info.h"
#include "src/ui/lib/escher/vk/texture.h"
namespace {
using namespace escher;
constexpr uint32_t kWidth = 1024;
constexpr uint32_t kHeight = 1024;
struct FramebufferTextures {
TexturePtr color1;
TexturePtr color2;
TexturePtr depth;
};
std::vector<FramebufferTextures> MakeFramebufferTextures(
Escher* escher, size_t count, uint32_t width, uint32_t height, uint32_t sample_count,
vk::Format color_format1, vk::Format color_format2, vk::Format depth_format) {
std::vector<FramebufferTextures> result;
result.reserve(count);
for (size_t i = 0; i < count; ++i) {
FramebufferTextures textures;
if (color_format1 != vk::Format::eUndefined) {
textures.color1 =
escher->NewAttachmentTexture(color_format1, width, height, 1, vk::Filter::eNearest);
}
if (color_format2 != vk::Format::eUndefined) {
textures.color2 =
escher->NewAttachmentTexture(color_format2, width, height, 1, vk::Filter::eNearest);
}
if (depth_format != vk::Format::eUndefined) {
textures.depth =
escher->NewAttachmentTexture(depth_format, width, height, 1, vk::Filter::eNearest);
}
result.push_back(std::move(textures));
}
return result;
}
RenderPassInfo MakeRenderPassInfo(const FramebufferTextures& textures) {
RenderPassInfo info;
info.num_color_attachments = 0;
if (textures.color1) {
info.color_attachments[info.num_color_attachments++] = textures.color1;
}
if (textures.color2) {
info.color_attachments[info.num_color_attachments++] = textures.color2;
}
info.depth_stencil_attachment = textures.depth;
RenderPassInfo::InitRenderPassAttachmentInfosFromImages(&info);
EXPECT_TRUE(info.Validate());
return info;
}
std::vector<impl::FramebufferPtr> ObtainFramebuffers(
impl::FramebufferAllocator* allocator, const std::vector<FramebufferTextures>& textures) {
std::vector<impl::FramebufferPtr> result;
result.reserve(textures.size());
for (auto& texs : textures) {
result.push_back(allocator->ObtainFramebuffer(MakeRenderPassInfo(texs), true));
}
return result;
}
using FramebufferAllocatorTest = test::TestWithVkValidationLayer;
VK_TEST_F(FramebufferAllocatorTest, Basic) {
auto escher = test::GetEscher();
impl::RenderPassCache cache(escher->resource_recycler());
impl::FramebufferAllocator allocator(escher->resource_recycler(), &cache);
allocator.BeginFrame();
std::set<vk::Format> supported_depth_formats =
escher->device()->caps().GetAllMatchingDepthStencilFormats(
{vk::Format::eD24UnormS8Uint, vk::Format::eD32SfloatS8Uint});
bool d24_supported =
supported_depth_formats.find(vk::Format::eD24UnormS8Uint) != supported_depth_formats.end();
bool d32_supported =
supported_depth_formats.find(vk::Format::eD32SfloatS8Uint) != supported_depth_formats.end();
// Create a pair of each of three types of framebuffers.
auto textures_2colors_D24 = MakeFramebufferTextures(
escher, 2, kWidth, kHeight, 1, vk::Format::eB8G8R8A8Unorm, vk::Format::eB8G8R8A8Unorm,
d24_supported ? vk::Format::eD24UnormS8Uint : vk::Format::eUndefined);
auto textures_2colors_D32 = MakeFramebufferTextures(
escher, 2, kWidth, kHeight, 1, vk::Format::eB8G8R8A8Unorm, vk::Format::eB8G8R8A8Unorm,
d32_supported ? vk::Format::eD32SfloatS8Uint : vk::Format::eUndefined);
auto textures_1color_D32 = MakeFramebufferTextures(
escher, 2, kWidth, kHeight, 1, vk::Format::eB8G8R8A8Unorm, vk::Format::eUndefined,
d32_supported ? vk::Format::eD32SfloatS8Uint : vk::Format::eUndefined);
auto framebuffers_2colors_D24 = ObtainFramebuffers(&allocator, textures_2colors_D24);
auto framebuffers_2colors_D32 = ObtainFramebuffers(&allocator, textures_2colors_D32);
auto framebuffers_1color_D32 = ObtainFramebuffers(&allocator, textures_1color_D32);
ASSERT_TRUE(framebuffers_2colors_D24[0] && framebuffers_2colors_D24[1]);
ASSERT_TRUE(framebuffers_2colors_D32[0] && framebuffers_2colors_D32[1]);
ASSERT_TRUE(framebuffers_1color_D32[0] && framebuffers_1color_D32[1]);
// Each pair should have two different Framebuffers which share the same
// RenderPass.
EXPECT_NE(framebuffers_2colors_D24[0], framebuffers_2colors_D24[1]);
EXPECT_EQ(framebuffers_2colors_D24[0]->render_pass(), framebuffers_2colors_D24[1]->render_pass());
EXPECT_NE(framebuffers_2colors_D32[0], framebuffers_2colors_D32[1]);
EXPECT_EQ(framebuffers_2colors_D32[0]->render_pass(), framebuffers_2colors_D32[1]->render_pass());
EXPECT_NE(framebuffers_1color_D32[0], framebuffers_1color_D32[1]);
EXPECT_EQ(framebuffers_1color_D32[0]->render_pass(), framebuffers_1color_D32[1]->render_pass());
// If either D32 or D24 format is supported we will have different textures
// for textures_2colors_D24 and textures_2colors_D32, so the render passes
// will be different; otherwise they will be the same.
// The rest pairs of Framebuffers should have different RenderPasses since the
// color formats are different.
if (d32_supported || d24_supported) {
EXPECT_EQ(cache.size(), 3U);
EXPECT_NE(framebuffers_2colors_D24[0]->render_pass(),
framebuffers_2colors_D32[0]->render_pass());
} else {
EXPECT_EQ(cache.size(), 2U);
EXPECT_EQ(framebuffers_2colors_D24[0]->render_pass(),
framebuffers_2colors_D32[0]->render_pass());
}
EXPECT_NE(framebuffers_2colors_D24[0]->render_pass(), framebuffers_1color_D32[0]->render_pass());
EXPECT_NE(framebuffers_2colors_D32[0]->render_pass(), framebuffers_1color_D32[0]->render_pass());
// TODO(fxbug.dev/36827) Now Vulkan validation layer has a performance warning:
// [ UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout ]
// Layout for color attachment is GENERAL but should be COLOR_ATTACHMENT_OPTIMAL.
SUPPRESS_VK_VALIDATION_PERFORMANCE_WARNINGS();
}
// Specificially test that we can create render-passes/framebuffers with no depth attachment.
// This will overlap with the "Basic" test on targets which don't support depth attachments,
// but we want to test this on all targets.
VK_TEST_F(FramebufferAllocatorTest, BasicNoDepth) {
auto escher = test::GetEscher();
impl::RenderPassCache cache(escher->resource_recycler());
impl::FramebufferAllocator allocator(escher->resource_recycler(), &cache);
allocator.BeginFrame();
// Create a pair of each of two types of framebuffers.
auto textures_2colors =
MakeFramebufferTextures(escher, 2, kWidth, kHeight, 1, vk::Format::eB8G8R8A8Unorm,
vk::Format::eB8G8R8A8Unorm, vk::Format::eUndefined);
auto textures_1color =
MakeFramebufferTextures(escher, 2, kWidth, kHeight, 1, vk::Format::eB8G8R8A8Unorm,
vk::Format::eUndefined, vk::Format::eUndefined);
auto framebuffers_2colors = ObtainFramebuffers(&allocator, textures_2colors);
auto framebuffers_1color = ObtainFramebuffers(&allocator, textures_1color);
ASSERT_TRUE(framebuffers_2colors[0] && framebuffers_2colors[1]);
ASSERT_TRUE(framebuffers_1color[0] && framebuffers_1color[1]);
// Each pair should have two different Framebuffers which share the same
// RenderPass.
EXPECT_NE(framebuffers_2colors[0], framebuffers_2colors[1]);
EXPECT_EQ(framebuffers_2colors[0]->render_pass(), framebuffers_2colors[1]->render_pass());
EXPECT_NE(framebuffers_1color[0], framebuffers_1color[1]);
EXPECT_EQ(framebuffers_1color[0]->render_pass(), framebuffers_1color[1]->render_pass());
EXPECT_NE(framebuffers_2colors[0]->render_pass(), framebuffers_1color[0]->render_pass());
// TODO(fxbug.dev/36827) Now Vulkan validation layer has a performance warning:
// [ UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout ]
// Layout for color attachment is GENERAL but should be COLOR_ATTACHMENT_OPTIMAL.
SUPPRESS_VK_VALIDATION_PERFORMANCE_WARNINGS();
}
// Test that we can create render-passes/framebuffers with no color attachment, only a depth
// attachment. This is useful for e.g. rendering shadow maps.
VK_TEST_F(FramebufferAllocatorTest, DepthOnly) {
auto escher = test::GetEscher();
impl::RenderPassCache cache(escher->resource_recycler());
impl::FramebufferAllocator allocator(escher->resource_recycler(), &cache);
allocator.BeginFrame();
std::set<vk::Format> supported_depth_formats =
escher->device()->caps().GetAllMatchingDepthStencilFormats(
{vk::Format::eD24UnormS8Uint, vk::Format::eD32SfloatS8Uint});
if (supported_depth_formats.empty()) {
FX_LOGS(WARNING) << "No depth formats supported, skipping test.";
return;
}
vk::Format supported_depth_format = *(supported_depth_formats.begin());
// Create a pair of each of three types of framebuffers.
auto textures = MakeFramebufferTextures(escher, 2, kWidth, kHeight, 1, vk::Format::eUndefined,
vk::Format::eUndefined, supported_depth_format);
auto framebuffers = ObtainFramebuffers(&allocator, textures);
// Each pair should have two different Framebuffers which share the same
// RenderPass.
ASSERT_TRUE(framebuffers[0] && framebuffers[1]);
EXPECT_NE(framebuffers[0], framebuffers[1]);
EXPECT_EQ(framebuffers[0]->render_pass(), framebuffers[1]->render_pass());
}
VK_TEST_F(FramebufferAllocatorTest, CacheReclamation) {
auto escher = test::GetEscher();
impl::RenderPassCache cache(escher->resource_recycler());
impl::FramebufferAllocator allocator(escher->resource_recycler(), &cache);
allocator.BeginFrame();
// Make a single set of textures (depth and 2 color attachments) that will be
// used to make a framebuffer.
auto depth_format_result = escher->device()->caps().GetMatchingDepthFormat();
vk::Format depth_format = depth_format_result.value;
if (depth_format_result.result != vk::Result::eSuccess) {
FX_LOGS(ERROR) << "No depth stencil format is supported on this device.";
depth_format = vk::Format::eUndefined;
}
auto textures = MakeFramebufferTextures(escher, 1, kWidth, kHeight, 1, vk::Format::eB8G8R8A8Unorm,
vk::Format::eB8G8R8A8Unorm, depth_format);
auto framebuffer = ObtainFramebuffers(&allocator, textures);
// Obtaining a Framebuffer using the same textures should result in the same
// Framebuffer.
EXPECT_EQ(framebuffer, ObtainFramebuffers(&allocator, textures));
// ... this should still be true on the following frame.
allocator.BeginFrame();
EXPECT_EQ(framebuffer, ObtainFramebuffers(&allocator, textures));
// ... in fact, Framebuffers should not be evicted from the cache as long as
// the number of frames since last use is < kFramesUntilEviction.
constexpr uint32_t kNotEnoughFramesForEviction = 4;
for (uint32_t i = 0; i < kNotEnoughFramesForEviction; ++i) {
allocator.BeginFrame();
}
EXPECT_EQ(framebuffer, ObtainFramebuffers(&allocator, textures));
// ... but one more frame than that will cause a different Framebuffer to be
// obtained from the allocator.
constexpr uint32_t kJustEnoughFramesForEviction = kNotEnoughFramesForEviction + 1;
for (uint32_t i = 0; i < kJustEnoughFramesForEviction; ++i) {
allocator.BeginFrame();
}
EXPECT_NE(framebuffer, ObtainFramebuffers(&allocator, textures));
// TODO(fxbug.dev/36827) Now Vulkan validation layer has a performance warning:
// [ UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout ]
// Layout for color attachment is GENERAL but should be COLOR_ATTACHMENT_OPTIMAL.
SUPPRESS_VK_VALIDATION_PERFORMANCE_WARNINGS();
}
VK_TEST_F(FramebufferAllocatorTest, LazyRenderPassCreation) {
auto escher = test::GetEscher();
impl::RenderPassCache rp_cache(escher->resource_recycler());
impl::FramebufferAllocator allocator(escher->resource_recycler(), &rp_cache);
allocator.BeginFrame();
// Make a single set of textures (depth and 2 color attachments) that will be
// used to make a framebuffer.
auto depth_format_result = escher->device()->caps().GetMatchingDepthFormat();
vk::Format depth_format = depth_format_result.value;
if (depth_format_result.result != vk::Result::eSuccess) {
FX_LOGS(ERROR) << "No depth stencil format is supported on this device.";
depth_format = vk::Format::eUndefined;
}
auto textures_bgra =
MakeFramebufferTextures(escher, 2, kWidth, kHeight, 1, vk::Format::eB8G8R8A8Unorm,
vk::Format::eB8G8R8A8Unorm, depth_format);
auto textures_rgba =
MakeFramebufferTextures(escher, 1, kWidth, kHeight, 1, vk::Format::eR8G8B8A8Unorm,
vk::Format::eR8G8B8A8Unorm, depth_format);
RenderPassInfo rpi_bgra0 = MakeRenderPassInfo(textures_bgra[0]);
RenderPassInfo rpi_bgra1 = MakeRenderPassInfo(textures_bgra[1]);
RenderPassInfo rpi_rgba0 = MakeRenderPassInfo(textures_rgba[0]);
// No framebuffer obtained, because there is no render-pass yet.
FX_LOGS(INFO) << "============= NOTE: Escher warnings expected";
auto fb_bgra0 = allocator.ObtainFramebuffer(rpi_bgra0, false);
EXPECT_FALSE(fb_bgra0);
EXPECT_EQ(0U, allocator.size());
EXPECT_EQ(0U, rp_cache.size());
FX_LOGS(INFO) << "============= NOTE: no additional Escher warnings are expected\n";
// This time, we allow lazy render-pass creation.
fb_bgra0 = allocator.ObtainFramebuffer(rpi_bgra0, true);
EXPECT_TRUE(fb_bgra0);
// We can find the same framebuffer again, regardless of whether lazy render-pass creation is
// allowed.
EXPECT_EQ(fb_bgra0, allocator.ObtainFramebuffer(rpi_bgra0, false));
EXPECT_EQ(fb_bgra0, allocator.ObtainFramebuffer(rpi_bgra0, true));
EXPECT_EQ(1U, allocator.size());
EXPECT_EQ(1U, rp_cache.size());
// We can also obtain a new framebuffer, even if we disallow lazy render-pass creation (since the
// existing render-pass will be found/used again).
auto fb_bgra1 = allocator.ObtainFramebuffer(rpi_bgra1, false);
EXPECT_TRUE(fb_bgra1);
EXPECT_NE(fb_bgra0, fb_bgra1);
EXPECT_EQ(2U, allocator.size());
EXPECT_EQ(1U, rp_cache.size());
EXPECT_EQ(fb_bgra0->render_pass(), fb_bgra1->render_pass());
// Using an incompatible RenderPassInfo, disabling lazy render-pass creation means that we can't
// obtain a framebuffer.
FX_LOGS(INFO) << "============= NOTE: Escher warnings expected";
auto fb_rgba0 = allocator.ObtainFramebuffer(rpi_rgba0, false);
EXPECT_FALSE(fb_rgba0);
EXPECT_EQ(2U, allocator.size());
EXPECT_EQ(1U, rp_cache.size());
FX_LOGS(INFO) << "============= NOTE: no additional Escher warnings are expected\n";
// And of course, enabling lazy render-pass creation will allow us to obtain a framebuffer.
fb_rgba0 = allocator.ObtainFramebuffer(rpi_rgba0, true);
EXPECT_TRUE(fb_rgba0);
EXPECT_EQ(3U, allocator.size());
EXPECT_EQ(2U, rp_cache.size());
EXPECT_NE(fb_rgba0->render_pass(), fb_bgra0->render_pass());
EXPECT_NE(fb_rgba0->render_pass(), fb_bgra1->render_pass());
// TODO(fxbug.dev/36827) Now Vulkan validation layer has a performance warning:
// [ UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout ]
// Layout for color attachment is GENERAL but should be COLOR_ATTACHMENT_OPTIMAL.
SUPPRESS_VK_VALIDATION_PERFORMANCE_WARNINGS();
}
} // anonymous namespace