blob: 70a09933b17a74e21284b588a9772c72defbac55 [file] [log] [blame]
// Copyright 2019 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/flatland/rectangle_compositor.h"
#include <gtest/gtest.h>
#include "src/ui/lib/escher/defaults/default_shader_program_factory.h"
#include "src/ui/lib/escher/flatland/flatland_static_config.h"
#include "src/ui/lib/escher/renderer/batch_gpu_uploader.h"
#include "src/ui/lib/escher/renderer/frame.h"
#include "src/ui/lib/escher/renderer/render_funcs.h"
#include "src/ui/lib/escher/resources/resource.h"
#include "src/ui/lib/escher/resources/resource_manager.h"
#include "src/ui/lib/escher/test/common/gtest_escher.h"
#include "src/ui/lib/escher/test/common/readback_test.h"
#include "src/ui/lib/escher/types/color.h"
#include "src/ui/lib/escher/types/color_histogram.h"
#include "src/ui/lib/escher/vk/texture.h"
namespace escher {
// Default 1x1 texture for Renderables with no texture.
TexturePtr CreateWhiteTexture(EscherWeakPtr escher, BatchGpuUploader* gpu_uploader) {
FX_DCHECK(escher);
uint8_t channels[4];
channels[0] = channels[1] = channels[2] = channels[3] = 255;
auto image = escher->NewRgbaImage(gpu_uploader, 1, 1, channels);
return escher->NewTexture(std::move(image), vk::Filter::eNearest);
}
// 2x2 texture with white, red, green and blue pixels.
TexturePtr CreateFourColorTexture(EscherWeakPtr escher, BatchGpuUploader* gpu_uploader) {
FX_DCHECK(escher);
uint8_t channels[16] = {255, 255, 255, 255, 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255};
auto image = escher->NewRgbaImage(gpu_uploader, 2, 2, channels);
return escher->NewTexture(std::move(image), vk::Filter::eNearest);
};
TexturePtr CreateDepthBuffer(Escher* escher, const ImagePtr& output_image) {
TexturePtr depth_buffer;
RenderFuncs::ObtainDepthTexture(
escher, output_image->use_protected_memory(), output_image->info(),
escher->device()->caps().GetMatchingDepthStencilFormat().value, depth_buffer);
return depth_buffer;
}
// Extends ReadbackTest to allow for quick testing of RectangleCompositor.
class RectangleCompositorTest : public ReadbackTest {
protected:
// |ReadbackTest|
void SetUp() override {
ReadbackTest::SetUp();
escher()->shader_program_factory()->filesystem()->InitializeWithRealFiles(kFlatlandShaderPaths);
ren_ = std::make_unique<RectangleCompositor>(escher().get());
frame_setup();
auto gpu_uploader =
std::make_shared<escher::BatchGpuUploader>(escher(), frame_data_.frame->frame_number());
auto cmd_buf = frame_data_.frame->cmds();
auto upload_semaphore = escher::Semaphore::New(escher()->vk_device());
gpu_uploader->AddSignalSemaphore(upload_semaphore);
default_texture_ = CreateWhiteTexture(escher(), gpu_uploader.get());
cmd_buf->AddWaitSemaphore(std::move(upload_semaphore),
vk::PipelineStageFlagBits::eVertexInput |
vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eColorAttachmentOutput |
vk::PipelineStageFlagBits::eTransfer);
gpu_uploader->Submit();
frame_data_.frame->EndFrame(SemaphorePtr(), []() {});
}
// |ReadbackTest|
void TearDown() override {
frame_data_.frame->EndFrame(SemaphorePtr(), []() {});
escher()->vk_device().waitIdle();
ASSERT_TRUE(escher()->Cleanup());
ren_.reset();
ReadbackTest::TearDown();
}
// Sets up the environment.
void frame_setup() { frame_data_ = NewFrame(vk::ImageLayout::eColorAttachmentOptimal); };
escher::RectangleCompositor* renderer() const { return ren_.get(); }
public:
std::unique_ptr<escher::RectangleCompositor> ren_;
// Frame environment variables.
ReadbackTest::FrameData frame_data_;
// Default texture;
TexturePtr default_texture_;
// Common colors used between tests.
static constexpr ColorBgra kWhite = ColorBgra(255, 255, 255, 255);
static constexpr ColorBgra kRed = ColorBgra(255, 0, 0, 255);
static constexpr ColorBgra kGreen = ColorBgra(0, 255, 0, 255);
static constexpr ColorBgra kBlue = ColorBgra(0, 0, 255, 255);
static constexpr ColorBgra kBlack = ColorBgra(0, 0, 0, 0);
};
// Render a single renderable using the RectangleCompositor. It should
// render as a single white rectangle.
VK_TEST_F(RectangleCompositorTest, SingleRenderableTest) {
frame_setup();
EXPECT_TRUE(ren_);
Rectangle2D rectangle(vec2(150, 200), vec2(100, 300));
RectangleCompositor::ColorData color_data(vec4(1), /*is_transparent*/ false);
auto cmd_buf = frame_data_.frame->cmds();
auto depth_texture = CreateDepthBuffer(escher().get(), frame_data_.color_attachment);
ren_->DrawBatch(cmd_buf, {rectangle}, {default_texture_}, {color_data},
frame_data_.color_attachment, depth_texture);
auto bytes = ReadbackFromColorAttachment(frame_data_.frame,
frame_data_.color_attachment->swapchain_layout(),
vk::ImageLayout::eColorAttachmentOptimal);
const ColorHistogram<ColorBgra> histogram(bytes.data(), kFramebufferWidth * kFramebufferHeight);
constexpr ColorBgra kWhite(255, 255, 255, 255);
constexpr ColorBgra kBlack(0, 0, 0, 0);
EXPECT_EQ(2U, histogram.size());
EXPECT_EQ(histogram[kWhite], 30000U); // 100x300.
EXPECT_EQ(histogram[kBlack], (512U * 512U - 30000U));
}
// Render a single full-screen renderable with a texture that has 4 colors.
VK_TEST_F(RectangleCompositorTest, SimpleTextureTest) {
frame_setup();
auto gpu_uploader =
std::make_shared<escher::BatchGpuUploader>(escher(), frame_data_.frame->frame_number());
EXPECT_TRUE(gpu_uploader);
EXPECT_TRUE(ren_);
auto texture = CreateFourColorTexture(escher(), gpu_uploader.get());
gpu_uploader->Submit();
Rectangle2D rectangle(vec2(0, 0), vec2(512, 512));
RectangleCompositor::ColorData color_data(vec4(1), /*is_transparent*/ false);
auto cmd_buf = frame_data_.frame->cmds();
auto depth_texture = CreateDepthBuffer(escher().get(), frame_data_.color_attachment);
ren_->DrawBatch(cmd_buf, {rectangle}, {texture}, {color_data}, frame_data_.color_attachment,
depth_texture);
auto bytes = ReadbackFromColorAttachment(frame_data_.frame,
frame_data_.color_attachment->swapchain_layout(),
vk::ImageLayout::eColorAttachmentOptimal);
const ColorHistogram<ColorBgra> histogram(bytes.data(), kFramebufferWidth * kFramebufferHeight);
constexpr uint32_t num_pixels = 512 * 512 / 4;
EXPECT_EQ(4U, histogram.size());
EXPECT_EQ(histogram[kWhite], num_pixels);
EXPECT_EQ(histogram[kRed], num_pixels);
EXPECT_EQ(histogram[kGreen], num_pixels);
EXPECT_EQ(histogram[kBlue], num_pixels);
}
// Render a single full-screen renderable that is rotated by 90 degrees
// and shifted so that it is half off the screen to the right. This should
// make it so that only 2 out of the 4 texture colors display, and those 2
// colors should be the proper colors post-rotation.
// Prerotation:
// | W R |
// | G B |
///
// Post rotation:
// | G W |
// | B R |
//
// When this post-rotation renderable is shifted to the right hand of the screen,
// only the green and blue colors should show.
VK_TEST_F(RectangleCompositorTest, RotatedTextureTest) {
frame_setup();
auto gpu_uploader =
std::make_shared<escher::BatchGpuUploader>(escher(), frame_data_.frame->frame_number());
EXPECT_TRUE(gpu_uploader);
EXPECT_TRUE(ren_);
auto texture = CreateFourColorTexture(escher(), gpu_uploader.get());
gpu_uploader->Submit();
Rectangle2D rectangle(vec2(256, 0), vec2(512, 512),
{vec2(0, 1), vec2(0, 0), vec2(1, 0), vec2(1, 1)});
RectangleCompositor::ColorData color_data(vec4(1), /*is_transparent*/ false);
auto cmd_buf = frame_data_.frame->cmds();
auto depth_texture = CreateDepthBuffer(escher().get(), frame_data_.color_attachment);
ren_->DrawBatch(cmd_buf, {rectangle}, {texture}, {color_data}, frame_data_.color_attachment,
depth_texture);
auto bytes = ReadbackFromColorAttachment(frame_data_.frame,
frame_data_.color_attachment->swapchain_layout(),
vk::ImageLayout::eColorAttachmentOptimal);
const ColorHistogram<ColorBgra> histogram(bytes.data(), kFramebufferWidth * kFramebufferHeight);
constexpr uint32_t num_pixels = 512 * 512 / 4;
// The three colors that should show are black (background), green and blue.
EXPECT_EQ(3U, histogram.size());
EXPECT_EQ(histogram[kWhite], 0U);
EXPECT_EQ(histogram[kRed], 0U);
EXPECT_EQ(histogram[kGreen], num_pixels);
EXPECT_EQ(histogram[kBlue], num_pixels);
}
// Render 4 rectangles side by side, each one taking up
// 1/4 of the entire frame. There should be no black pixels
// and each rectangle should have the same exact number of
// pixels covered.
VK_TEST_F(RectangleCompositorTest, MultiRenderableTest) {
frame_setup();
EXPECT_TRUE(ren_);
std::vector<Rectangle2D> rectangles;
std::vector<RectangleCompositor::ColorData> color_datas;
std::vector<const TexturePtr> textures;
vec4 colors[4] = {vec4{1, 0, 0, 1}, vec4(0, 1, 0, 1), vec4(0, 0, 1, 1), vec4(1, 1, 1, 1)};
for (uint32_t i = 0; i < 4; i++) {
Rectangle2D rectangle(vec2(128 * i, 0), vec2(128, 512));
RectangleCompositor::ColorData color_data(colors[i], /*is_transparent*/ false);
rectangles.emplace_back(rectangle);
color_datas.emplace_back(color_data);
textures.emplace_back(default_texture_);
}
auto cmd_buf = frame_data_.frame->cmds();
auto depth_texture = CreateDepthBuffer(escher().get(), frame_data_.color_attachment);
ren_->DrawBatch(cmd_buf, rectangles, textures, color_datas, frame_data_.color_attachment,
depth_texture);
auto bytes = ReadbackFromColorAttachment(frame_data_.frame,
frame_data_.color_attachment->swapchain_layout(),
vk::ImageLayout::eColorAttachmentOptimal);
const ColorHistogram<ColorBgra> histogram(bytes.data(), kFramebufferWidth * kFramebufferHeight);
size_t pixels_per_color = 128 * 512;
EXPECT_EQ(4U, histogram.size());
EXPECT_EQ(histogram[kRed], pixels_per_color);
EXPECT_EQ(histogram[kGreen], pixels_per_color);
EXPECT_EQ(histogram[kBlue], pixels_per_color);
EXPECT_EQ(histogram[kWhite], pixels_per_color);
EXPECT_EQ(histogram[kBlack], 0U);
}
// This test makes sure that depth is taken into account when
// rendering rectangles. Rectangle depth is implicit, with later
// rectangles being higher up than earlier rectangles. So this test
// renders two renderables, directly on top of eachother, red then
// green. Since the green one is inserted second, it should cover the
// red one, which should not have any pixels rendered.
VK_TEST_F(RectangleCompositorTest, OverlapTest) {
frame_setup();
EXPECT_TRUE(ren_);
std::vector<Rectangle2D> rectangles;
std::vector<RectangleCompositor::ColorData> color_datas;
std::vector<const TexturePtr> textures;
vec4 colors[2] = {vec4{1, 0, 0, 1}, vec4(0, 1, 0, 1)};
for (uint32_t i = 0; i < 2; i++) {
Rectangle2D rectangle(vec2(200, 200), vec2(100, 100));
RectangleCompositor::ColorData color_data(colors[i], /*is_transparent*/ false);
rectangles.emplace_back(rectangle);
color_datas.emplace_back(color_data);
textures.emplace_back(default_texture_);
}
auto cmd_buf = frame_data_.frame->cmds();
auto depth_texture = CreateDepthBuffer(escher().get(), frame_data_.color_attachment);
ren_->DrawBatch(cmd_buf, rectangles, textures, color_datas, frame_data_.color_attachment,
depth_texture);
auto bytes = ReadbackFromColorAttachment(frame_data_.frame,
frame_data_.color_attachment->swapchain_layout(),
vk::ImageLayout::eColorAttachmentOptimal);
const ColorHistogram<ColorBgra> histogram(bytes.data(), kFramebufferWidth * kFramebufferHeight);
size_t pixels_per_color = 100 * 100;
EXPECT_EQ(2U, histogram.size());
EXPECT_EQ(histogram[kRed], 0U);
EXPECT_EQ(histogram[kGreen], pixels_per_color);
EXPECT_EQ(histogram[kBlack], 512U * 512U - pixels_per_color);
}
// This test makes sure that alpha-blending transparency works.
// It renders a blue rectangle with 0.6 alpha on top of an
// opaque red rectangle.
// It does this test *twice*, once with is_transparent turned on
// and one with it off. Transparency should only be applied when
// the flag is on, even if the RectangleRenderable color has an
// alpha that is < 1.0.
// TODO (43394): Add testing for multiple interleaved opaque and
// transparent rectangles.
VK_TEST_F(RectangleCompositorTest, TransparencyTest) {
frame_setup();
EXPECT_TRUE(ren_);
std::vector<Rectangle2D> rectangles;
std::vector<RectangleCompositor::ColorData> color_datas;
std::vector<const TexturePtr> textures;
vec4 colors[2] = {vec4{1, 0, 0, 1}, vec4(0, 0, 1, 0.6)};
for (uint32_t i = 0; i < 2; i++) {
Rectangle2D rectangle(vec2(200, 200), vec2(100, 100));
RectangleCompositor::ColorData color_data(colors[i], /*is_transparent*/ true);
rectangles.emplace_back(rectangle);
color_datas.emplace_back(color_data);
textures.emplace_back(default_texture_);
}
auto cmd_buf = frame_data_.frame->cmds();
auto depth_texture = CreateDepthBuffer(escher().get(), frame_data_.color_attachment);
ren_->DrawBatch(cmd_buf, rectangles, textures, color_datas, frame_data_.color_attachment,
depth_texture);
auto bytes = ReadbackFromColorAttachment(frame_data_.frame,
frame_data_.color_attachment->swapchain_layout(),
vk::ImageLayout::eColorAttachmentOptimal);
const ColorHistogram<ColorBgra> histogram(bytes.data(), kFramebufferWidth * kFramebufferHeight);
// On Fuchsia the above transparency operation results in kBlend,
// but on LinuxHost it results in kBlend2. We check equality against
// both so that the test is robust regardless of platform.
constexpr ColorBgra kBlend(102, 0, 153, 255);
constexpr ColorBgra kBlend2(102, 0, 152, 255);
size_t pixels_per_color = 100 * 100;
EXPECT_EQ(2U, histogram.size());
EXPECT_EQ(histogram[kRed], 0U);
EXPECT_EQ(histogram[kBlue], 0U);
EXPECT_TRUE(histogram[kBlend] == pixels_per_color || histogram[kBlend2] == pixels_per_color);
EXPECT_EQ(histogram[kBlack], 512U * 512U - pixels_per_color);
}
// Turn the transparency flag off and try rendering with transparency again. Now
// even though the color has transparency, it should still render as opaque.
VK_TEST_F(RectangleCompositorTest, TransparencyFlagOffTest) {
frame_setup();
EXPECT_TRUE(ren_);
std::vector<Rectangle2D> rectangles;
std::vector<RectangleCompositor::ColorData> color_datas;
std::vector<const TexturePtr> textures;
vec4 colors[2] = {vec4{1, 0, 0, 1}, vec4(0, 0, 1, 0.6)};
for (uint32_t i = 0; i < 2; i++) {
Rectangle2D rectangle(vec2(200, 200), vec2(100, 100));
RectangleCompositor::ColorData color_data(colors[i], /*is_transparent*/ false);
rectangles.emplace_back(rectangle);
color_datas.emplace_back(color_data);
textures.emplace_back(default_texture_);
}
auto cmd_buf = frame_data_.frame->cmds();
auto depth_texture = CreateDepthBuffer(escher().get(), frame_data_.color_attachment);
ren_->DrawBatch(cmd_buf, rectangles, textures, color_datas, frame_data_.color_attachment,
depth_texture);
auto bytes = ReadbackFromColorAttachment(frame_data_.frame,
frame_data_.color_attachment->swapchain_layout(),
vk::ImageLayout::eColorAttachmentOptimal);
const ColorHistogram<ColorBgra> histogram2(bytes.data(), kFramebufferWidth * kFramebufferHeight);
constexpr ColorBgra kBlue2(0, 0, 153, 153); // Premultiplied alpha.
constexpr ColorBgra kBlend(102, 0, 153, 255);
constexpr ColorBgra kBlend2(102, 0, 152, 255);
size_t pixels_per_color = 100 * 100;
EXPECT_EQ(2U, histogram2.size());
EXPECT_EQ(histogram2[kRed], 0U);
EXPECT_EQ(histogram2[kBlue2], pixels_per_color);
EXPECT_TRUE(histogram2[kBlend] == pixels_per_color || histogram2[kBlend2] == 0);
EXPECT_EQ(histogram2[kBlack], 512U * 512U - pixels_per_color);
}
// Render 100 renderables.
VK_TEST_F(RectangleCompositorTest, StressTest) {
frame_setup();
EXPECT_TRUE(ren_);
std::vector<Rectangle2D> rectangles;
std::vector<RectangleCompositor::ColorData> color_datas;
std::vector<const TexturePtr> textures;
uint32_t max_renderables = 100;
for (uint32_t i = 0; i < max_renderables; i++) {
Rectangle2D rectangle(vec2(i, 0), vec2(1, 1));
RectangleCompositor::ColorData color_data(vec4(1, 0, 0, 1), /*is_transparent*/ false);
rectangles.emplace_back(rectangle);
color_datas.emplace_back(color_data);
textures.emplace_back(default_texture_);
}
auto cmd_buf = frame_data_.frame->cmds();
auto depth_texture = CreateDepthBuffer(escher().get(), frame_data_.color_attachment);
ren_->DrawBatch(cmd_buf, rectangles, textures, color_datas, frame_data_.color_attachment,
depth_texture);
auto bytes = ReadbackFromColorAttachment(frame_data_.frame,
frame_data_.color_attachment->swapchain_layout(),
vk::ImageLayout::eColorAttachmentOptimal);
const ColorHistogram<ColorBgra> histogram(bytes.data(), kFramebufferWidth * kFramebufferHeight);
EXPECT_EQ(2U, histogram.size());
EXPECT_EQ(histogram[kRed], 100U);
EXPECT_EQ(histogram[kBlack], 512U * 512U - 100U);
}
} // namespace escher