| // 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/debug/debug_rects.h" |
| #include "src/ui/lib/escher/defaults/default_shader_program_factory.h" |
| #include "src/ui/lib/escher/escher.h" |
| #include "src/ui/lib/escher/geometry/bounding_box.h" |
| #include "src/ui/lib/escher/impl/vulkan_utils.h" |
| #include "src/ui/lib/escher/material/material.h" |
| #include "src/ui/lib/escher/paper/paper_renderer.h" |
| #include "src/ui/lib/escher/paper/paper_renderer_static_config.h" |
| #include "src/ui/lib/escher/paper/paper_scene.h" |
| #include "src/ui/lib/escher/paper/paper_timestamp_graph.h" |
| #include "src/ui/lib/escher/renderer/batch_gpu_uploader.h" |
| #include "src/ui/lib/escher/test/common/paper_renderer_test.h" |
| #include "src/ui/lib/escher/types/color.h" |
| #include "src/ui/lib/escher/types/color_histogram.h" |
| #include "src/ui/lib/escher/util/image_utils.h" |
| |
| #include <vulkan/vulkan.hpp> |
| |
| namespace escher { |
| namespace test { |
| |
| namespace { |
| |
| ImagePtr CreateImageFrom1x1RgbaBytes(Escher* escher, std::array<uint8_t, 4> bytes) { |
| auto gpu_uploader = std::make_unique<escher::BatchGpuUploader>(escher->GetWeakPtr(), 0); |
| ImagePtr image = escher->NewRgbaImage(gpu_uploader.get(), 1, 1, bytes.data()); |
| gpu_uploader->Submit(); |
| ESCHER_DCHECK_VK_RESULT(escher->vk_device().waitIdle()); |
| return image; |
| } |
| |
| struct Result { |
| static const Result Ok() { return Result(); } |
| static const Result Err(std::string err) { return Result(err); } |
| |
| Result() : err(std::nullopt) {} |
| Result(std::string err_) : err(err_) {} |
| bool IsOk() { return !err.has_value(); } |
| bool IsErr() { return err.has_value(); } |
| |
| std::optional<std::string> err; |
| }; |
| |
| template <class ColorT> |
| bool ColorMatch(const ColorT x, const ColorT y, double eps = 0.05) { |
| static_assert(ColorT::color_depth() == 8); |
| return sqrt(((x.r - y.r) / 255.0 * (x.r - y.r) / 255.0) + |
| ((x.g - y.g) / 255.0 * (x.g - y.g) / 255.0) + |
| ((x.b - y.b) / 255.0 * (x.b - y.b) / 255.0) + |
| ((x.a - y.a) / 255.0 * (x.a - y.a) / 255.0)) < eps; |
| } |
| |
| template <class ColorT> |
| Result ExpectHistogramMatch(const ColorHistogram<ColorT>& hist_real, |
| const ColorHistogram<ColorT>& hist_expected, double eps = 1e-4) { |
| std::vector<ColorT> colors_real; |
| std::vector<ColorT> colors_expected; |
| size_t total_pixels_real = 0; |
| size_t total_pixels_expected = 0; |
| |
| for (const auto kv : hist_real.values) { |
| colors_real.push_back(kv.first); |
| total_pixels_real += kv.second; |
| } |
| for (const auto kv : hist_expected.values) { |
| colors_expected.push_back(kv.first); |
| total_pixels_expected += kv.second; |
| } |
| |
| for (const auto kv : hist_real.values) { |
| auto expected_key = |
| std::find_if(colors_expected.begin(), colors_expected.end(), |
| [color_x = kv.first](ColorT color_y) { return ColorMatch(color_x, color_y); }); |
| if (expected_key == colors_expected.end()) { |
| std::ostringstream err; |
| err << "Color " << kv.first << " not found. \n" |
| << "Histogram: " << hist_real << ";\n" |
| << "Expected: " << hist_expected; |
| return Result::Err(err.str()); |
| } |
| |
| double ratio_expected = hist_expected[*expected_key] / double(total_pixels_expected); |
| double ratio_real = kv.second / double(total_pixels_real); |
| |
| if (fabs(ratio_real - ratio_expected) > eps) { |
| std::ostringstream err; |
| err << "Ratio of color " << kv.first << " doesn't match." |
| << " Expected ratio: " << hist_expected[*expected_key] << "/" |
| << double(total_pixels_expected) << ", real ratio: " << kv.second << "/" |
| << double(total_pixels_real) << "\n" |
| << "Histogram: " << hist_real << ";\n" |
| << "Expected: " << hist_expected; |
| return Result::Err(err.str()); |
| } |
| } |
| return Result::Ok(); |
| } |
| |
| #define EXPECT_RESULT_OK(expr) \ |
| do { \ |
| Result result = (expr); \ |
| EXPECT_TRUE(result.IsOk()) << *result.err; \ |
| } while (0); |
| |
| } // namespace |
| |
| using OpacityShapeTest = PaperRendererTest; |
| |
| // We draw the following scene: |
| // +--------+---------+---------+---------+ |
| // | | | |
| // | | | |
| // | | | |
| // + YELLOW | BLACK | |
| // | (1, 1, 0, 1) | | |
| // | | | |
| // | | | |
| // + ====================| |
| // | ǁ | | |
| // | ǁ Blend | | |
| // | ǁ | | |
| // +------------------ǁ---------+ | |
| // | BLACK ǁ Cyan (75%) | |
| // | (0, 0, 0, 1) ǁ (0, 1, 1, 0.75) | |
| // | ǁ | |
| // +--------+---------+---------+---------+ |
| // |
| // The Cyan rectangle is over Yellow rectangle, which |
| // is over the black background. |
| // |
| // 8/16 of the area should be Yellow (1, 1, 0, 1); |
| // 4/16 of the area should be Black (0, 0 ,0, 1); |
| // 3/16 of the area should be 75% Cyan (0, 0.75, 0.75, 1); |
| // 1/16 of the area should be the blended color, which is |
| // (0.25 * (1, 1, 0) + 0.75 * (0, 1, 1), 1) = (0.25, 1, 0.75, 1). |
| // |
| VK_TEST_F(OpacityShapeTest, TranslucentOverOpaque) { |
| const glm::vec4 kYellow(1, 1, 0, 1); |
| const glm::vec4 kCyan75(0, 1, 1, 0.75); |
| const glm::vec4 kBlack(0, 0, 0, 1); |
| |
| SetupFrame(); |
| BeginRenderingFrame(); |
| escher::PaperTransformStack* transform_stack = renderer()->transform_stack(); |
| |
| transform_stack->PushTranslation(vec2(0, 0)); |
| { |
| transform_stack->PushElevation(0); |
| vec2 top_left(0, 0); |
| vec2 bottom_right(kFramebufferWidth, kFramebufferHeight); |
| renderer()->DrawRect(top_left, bottom_right, Material::New(kBlack)); |
| transform_stack->Pop(); |
| } |
| { |
| transform_stack->PushElevation(-1); |
| vec2 top_left(0, 0); |
| vec2 bottom_right(kFramebufferWidth * 3 / 4, kFramebufferHeight * 3 / 4); |
| renderer()->DrawRect(top_left, bottom_right, Material::New(kYellow)); |
| transform_stack->Pop(); |
| } |
| { |
| transform_stack->PushElevation(-2); |
| vec2 top_left(kFramebufferWidth / 2, kFramebufferHeight / 2); |
| vec2 bottom_right(kFramebufferWidth, kFramebufferHeight); |
| MaterialPtr material = Material::New(kCyan75); |
| material->set_type(Material::Type::kTranslucent); |
| renderer()->DrawRect(top_left, bottom_right, material); |
| transform_stack->Pop(); |
| } |
| |
| EndRenderingFrame(); |
| EXPECT_VK_SUCCESS(escher()->vk_device().waitIdle()); |
| |
| auto bytes = GetPixelData(); |
| const ColorHistogram<ColorBgra> histogram(bytes.data(), kFramebufferWidth * kFramebufferHeight); |
| |
| auto expected_histogram = ColorHistogram<ColorBgra>({{ColorBgra(0xFF, 0xFF, 0x00, 0xFF), 8u}, |
| {ColorBgra(0x00, 0x00, 0x00, 0xFF), 4u}, |
| {ColorBgra(0x00, 0xBF, 0xBF, 0xFF), 3u}, |
| {ColorBgra(0x3F, 0xFF, 0xBF, 0xFF), 1u}}); |
| |
| EXPECT_RESULT_OK(ExpectHistogramMatch(histogram, expected_histogram)); |
| |
| frame_data().frame->EndFrame(SemaphorePtr(), []() {}); |
| } |
| |
| // We draw the following scene: |
| // +--------+---------+---------+---------+ |
| // | | | |
| // | | | |
| // | | | |
| // + Cyan(75%) | BLACK | |
| // | (0, 1, 1, 0.75) | | |
| // | | | |
| // | | | |
| // + ====================| |
| // | ǁ | |
| // | ǁ | |
| // | ǁ Yellow | |
| // +------------------ǁ (1, 1, 0, 1) | |
| // | BLACK ǁ | |
| // | (0, 0, 0, 1) ǁ | |
| // | ǁ | |
| // +--------+---------+---------+---------+ |
| // |
| // The Yellow rectangle is over Cyan rectangle, which |
| // is over the black background. |
| // |
| // 4/16 of the area should be Yellow (1, 1, 0, 1); |
| // 4/16 of the area should be Black (0, 0 ,0, 1); |
| // 8/16 of the area should be 75% Cyan (0, 0.75, 0.75, 1); |
| // |
| VK_TEST_F(OpacityShapeTest, OpaqueOverTranslucent) { |
| const glm::vec4 kYellow(1, 1, 0, 1); |
| const glm::vec4 kCyan75(0, 1, 1, 0.75); |
| const glm::vec4 kBlack(0, 0, 0, 1); |
| |
| SetupFrame(); |
| BeginRenderingFrame(); |
| escher::PaperTransformStack* transform_stack = renderer()->transform_stack(); |
| |
| transform_stack->PushTranslation(vec2(0, 0)); |
| { |
| transform_stack->PushElevation(0); |
| vec2 top_left(0, 0); |
| vec2 bottom_right(kFramebufferWidth, kFramebufferHeight); |
| renderer()->DrawRect(top_left, bottom_right, Material::New(kBlack)); |
| transform_stack->Pop(); |
| } |
| { |
| transform_stack->PushElevation(-2); |
| vec2 top_left(kFramebufferWidth / 2, kFramebufferHeight / 2); |
| vec2 bottom_right(kFramebufferWidth, kFramebufferHeight); |
| renderer()->DrawRect(top_left, bottom_right, Material::New(kYellow)); |
| transform_stack->Pop(); |
| } |
| { |
| transform_stack->PushElevation(-1); |
| vec2 top_left(0, 0); |
| vec2 bottom_right(kFramebufferWidth * 3 / 4, kFramebufferHeight * 3 / 4); |
| MaterialPtr material = Material::New(kCyan75); |
| material->set_type(Material::Type::kTranslucent); |
| renderer()->DrawRect(top_left, bottom_right, material); |
| transform_stack->Pop(); |
| } |
| |
| EndRenderingFrame(); |
| EXPECT_VK_SUCCESS(escher()->vk_device().waitIdle()); |
| |
| auto bytes = GetPixelData(); |
| const ColorHistogram<ColorBgra> histogram(bytes.data(), kFramebufferWidth * kFramebufferHeight); |
| |
| auto expected_histogram = ColorHistogram<ColorBgra>({{ColorBgra(0xFF, 0xFF, 0x00, 0xFF), 4u}, |
| {ColorBgra(0x00, 0x00, 0x00, 0xFF), 4u}, |
| {ColorBgra(0x00, 0xBF, 0xBF, 0xFF), 8u}}); |
| |
| EXPECT_RESULT_OK(ExpectHistogramMatch(histogram, expected_histogram)); |
| |
| frame_data().frame->EndFrame(SemaphorePtr(), []() {}); |
| } |
| |
| // We draw the following scene: |
| // +--------+---------+---------+---------+ |
| // | | | |
| // | | | |
| // | | | |
| // + Cyan(25%) | WHITE | |
| // | (0, 1, 1, 0.25) | | |
| // | | | |
| // | | | |
| // + ====================| |
| // | ǁ | | |
| // | ǁ Blend | | |
| // | ǁ | | |
| // +------------------ǁ---------+ | |
| // | WHITE ǁ Yellow 50% | |
| // | (1, 1, 1, 1) ǁ (1, 1, 0, 1) | |
| // | ǁ | |
| // +--------+---------+---------+---------+ |
| // |
| // The Yellow rectangle is over Cyan rectangle, which |
| // is over the white background. |
| // |
| // 8/16 of the area should be (0.75, 1, 1, 1); |
| // 4/16 of the area should be (1, 1, 1, 1); |
| // 3/16 of the area should be (1, 1, 0.5, 1); |
| // 1/16 of the area should be (0.825, 1, 0.5, 1). |
| // |
| VK_TEST_F(OpacityShapeTest, TranslucentOverTranslucent) { |
| const glm::vec4 kYellow50(1, 1, 0, 0.5); |
| const glm::vec4 kCyan25(0, 1, 1, 0.25); |
| const glm::vec4 kWhite(1, 1, 1, 1); |
| |
| SetupFrame(); |
| BeginRenderingFrame(); |
| escher::PaperTransformStack* transform_stack = renderer()->transform_stack(); |
| |
| transform_stack->PushTranslation(vec2(0, 0)); |
| { |
| transform_stack->PushElevation(0); |
| vec2 top_left(0, 0); |
| vec2 bottom_right(kFramebufferWidth, kFramebufferHeight); |
| renderer()->DrawRect(top_left, bottom_right, Material::New(kWhite)); |
| transform_stack->Pop(); |
| } |
| { |
| transform_stack->PushElevation(-1); |
| vec2 top_left(0, 0); |
| vec2 bottom_right(kFramebufferWidth * 3 / 4, kFramebufferHeight * 3 / 4); |
| MaterialPtr material = Material::New(kCyan25); |
| material->set_type(Material::Type::kTranslucent); |
| renderer()->DrawRect(top_left, bottom_right, material); |
| transform_stack->Pop(); |
| } |
| { |
| transform_stack->PushElevation(-2); |
| vec2 top_left(kFramebufferWidth / 2, kFramebufferHeight / 2); |
| vec2 bottom_right(kFramebufferWidth, kFramebufferHeight); |
| MaterialPtr material = Material::New(kYellow50); |
| material->set_type(Material::Type::kTranslucent); |
| renderer()->DrawRect(top_left, bottom_right, material); |
| transform_stack->Pop(); |
| } |
| |
| EndRenderingFrame(); |
| EXPECT_VK_SUCCESS(escher()->vk_device().waitIdle()); |
| |
| auto bytes = GetPixelData(); |
| const ColorHistogram<ColorBgra> histogram(bytes.data(), kFramebufferWidth * kFramebufferHeight); |
| |
| auto expected_histogram = ColorHistogram<ColorBgra>({{ColorBgra(0xBF, 0xFF, 0xFF, 0xFF), 8u}, |
| {ColorBgra(0xFF, 0xFF, 0xFF, 0xFF), 4u}, |
| {ColorBgra(0xFF, 0xFF, 0x7F, 0xFF), 3u}, |
| {ColorBgra(0xD3, 0xFF, 0x7F, 0xFF), 1u}}); |
| |
| EXPECT_RESULT_OK(ExpectHistogramMatch(histogram, expected_histogram)); |
| |
| TeardownFrame(); |
| } |
| |
| // We draw the following scene: |
| // +--------+---------+---------+---------+ |
| // | | | |
| // | | | |
| // | | | |
| // + Cyan(25%) | | |
| // | (0, 0.25, 0.25, 0.25) | | |
| // | | | |
| // | | | |
| // + | | |
| // | | | |
| // | | | |
| // | | | |
| // +----------------------------+ | |
| // | Fuchsia | |
| // | (1, 0, 1, 1) | |
| // | | |
| // +--------+---------+---------+---------+ |
| // |
| // The Cyan rectangle uses a premultiplied alpha texture, and it |
| // is over the white background. |
| // |
| // 9/16 of the area should be (0.75, 0.25, 1, 1); |
| // 7/16 of the area should be (1, 0, 1, 1); |
| // |
| VK_TEST_F(OpacityShapeTest, PremultipliedTexture) { |
| const glm::vec4 kFuchsia(1, 0, 1, 1); |
| |
| std::array<uint8_t, 4> cyan_25_premultiplied_bytes = {0x00, 0x40, 0x40, 0x40}; |
| ImagePtr cyan_25_image = CreateImageFrom1x1RgbaBytes(escher().get(), cyan_25_premultiplied_bytes); |
| TexturePtr cyan_25_texture = |
| Texture::New(escher()->resource_recycler(), cyan_25_image, vk::Filter::eNearest); |
| MaterialPtr cyan_25_material = Material::New(vec4(1, 1, 1, 1), cyan_25_texture); |
| cyan_25_material->set_type(Material::Type::kTranslucent); |
| |
| SetupFrame(); |
| BeginRenderingFrame(); |
| escher::PaperTransformStack* transform_stack = renderer()->transform_stack(); |
| |
| transform_stack->PushTranslation(vec2(0, 0)); |
| { |
| transform_stack->PushElevation(0); |
| vec2 top_left(0, 0); |
| vec2 bottom_right(kFramebufferWidth, kFramebufferHeight); |
| renderer()->DrawRect(top_left, bottom_right, Material::New(kFuchsia)); |
| transform_stack->Pop(); |
| } |
| { |
| transform_stack->PushElevation(-1); |
| vec2 top_left(0, 0); |
| vec2 bottom_right(kFramebufferWidth * 3 / 4, kFramebufferHeight * 3 / 4); |
| renderer()->DrawRect(top_left, bottom_right, cyan_25_material); |
| transform_stack->Pop(); |
| } |
| |
| EndRenderingFrame(); |
| EXPECT_VK_SUCCESS(escher()->vk_device().waitIdle()); |
| |
| auto bytes = GetPixelData(); |
| const ColorHistogram<ColorBgra> histogram(bytes.data(), kFramebufferWidth * kFramebufferHeight); |
| |
| auto expected_histogram = ColorHistogram<ColorBgra>( |
| {{ColorBgra(0xBF, 0x40, 0xFF, 0xFF), 9u}, {ColorBgra(0xFF, 0x00, 0xFF, 0xFF), 7u}}); |
| |
| EXPECT_RESULT_OK(ExpectHistogramMatch(histogram, expected_histogram)); |
| |
| TeardownFrame(); |
| } |
| |
| } // namespace test |
| } // namespace escher |