#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();

  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]);

  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 (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());

  SUPPRESS_VK_VALIDATION_PERFORMANCE_WARNINGS();
}

VK_TEST_F(FramebufferAllocatorTest, BasicNoDepth) {
  auto escher = test::GetEscher();

  impl::RenderPassCache cache(escher->resource_recycler());
  impl::FramebufferAllocator allocator(escher->resource_recycler(), &cache);
  allocator.BeginFrame();

  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]);

  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());

  SUPPRESS_VK_VALIDATION_PERFORMANCE_WARNINGS();
}

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());

  auto textures = MakeFramebufferTextures(escher, 2, kWidth, kHeight, 1, vk::Format::eUndefined,
                                          vk::Format::eUndefined, supported_depth_format);

  auto framebuffers = ObtainFramebuffers(&allocator, textures);

  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();

  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);

  EXPECT_EQ(framebuffer, ObtainFramebuffers(&allocator, textures));

  allocator.BeginFrame();
  EXPECT_EQ(framebuffer, ObtainFramebuffers(&allocator, textures));

  constexpr uint32_t kNotEnoughFramesForEviction = 4;
  for (uint32_t i = 0; i < kNotEnoughFramesForEviction; ++i) {
    allocator.BeginFrame();
  }
  EXPECT_EQ(framebuffer, ObtainFramebuffers(&allocator, textures));

  constexpr uint32_t kJustEnoughFramesForEviction = kNotEnoughFramesForEviction + 1;
  for (uint32_t i = 0; i < kJustEnoughFramesForEviction; ++i) {
    allocator.BeginFrame();
  }
  EXPECT_NE(framebuffer, ObtainFramebuffers(&allocator, textures));

  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();

  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]);

  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";

  fb_bgra0 = allocator.ObtainFramebuffer(rpi_bgra0, true);
  EXPECT_TRUE(fb_bgra0);

  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());

  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());

  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";

  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());

  SUPPRESS_VK_VALIDATION_PERFORMANCE_WARNINGS();
}

}
// 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
