| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #define FML_USED_ON_EMBEDDER |
| |
| #include <limits> |
| |
| #include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer.h" |
| #include "flutter/shell/platform/embedder/tests/embedder_unittests_util.h" |
| |
| namespace flutter { |
| namespace testing { |
| |
| sk_sp<SkSurface> CreateRenderSurface(const FlutterLayer& layer, |
| GrDirectContext* context) { |
| const auto image_info = |
| SkImageInfo::MakeN32Premul(layer.size.width, layer.size.height); |
| auto surface = context ? SkSurface::MakeRenderTarget( |
| context, // context |
| SkBudgeted::kNo, // budgeted |
| image_info, // image info |
| 1, // sample count |
| kTopLeft_GrSurfaceOrigin, // surface origin |
| nullptr, // surface properties |
| false // mipmaps |
| |
| ) |
| : SkSurface::MakeRaster(image_info); |
| FML_CHECK(surface != nullptr); |
| return surface; |
| } |
| |
| // Normalizes the color-space, color-type and alpha-type for comparison. |
| static sk_sp<SkData> NormalizeImage(sk_sp<SkImage> image) { |
| // To avoid clipping, convert to a very wide gamut, and a high bit depth. |
| sk_sp<SkColorSpace> norm_colorspace = SkColorSpace::MakeRGB( |
| SkNamedTransferFn::kRec2020, SkNamedGamut::kRec2020); |
| SkImageInfo norm_image_info = |
| SkImageInfo::Make(image->width(), image->height(), |
| SkColorType::kR16G16B16A16_unorm_SkColorType, |
| SkAlphaType::kUnpremul_SkAlphaType, norm_colorspace); |
| size_t row_bytes = norm_image_info.minRowBytes(); |
| size_t size = norm_image_info.computeByteSize(row_bytes); |
| sk_sp<SkData> data = SkData::MakeUninitialized(size); |
| if (!data) { |
| FML_CHECK(false) << "Unable to allocate data."; |
| } |
| |
| bool success = image->readPixels(norm_image_info, data->writable_data(), |
| row_bytes, 0, 0); |
| if (!success) { |
| FML_CHECK(false) << "Unable to read pixels."; |
| } |
| |
| return data; |
| } |
| |
| bool RasterImagesAreSame(sk_sp<SkImage> a, sk_sp<SkImage> b) { |
| if (!a || !b) { |
| return false; |
| } |
| |
| FML_CHECK(!a->isTextureBacked()); |
| FML_CHECK(!b->isTextureBacked()); |
| |
| sk_sp<SkData> normalized_a = NormalizeImage(a); |
| sk_sp<SkData> normalized_b = NormalizeImage(b); |
| |
| return normalized_a->equals(normalized_b.get()); |
| } |
| |
| std::string FixtureNameForBackend(EmbedderTestContextType backend, |
| const std::string& name) { |
| switch (backend) { |
| case EmbedderTestContextType::kVulkanContext: |
| return "vk_" + name; |
| default: |
| return name; |
| } |
| } |
| |
| EmbedderTestBackingStoreProducer::RenderTargetType GetRenderTargetFromBackend( |
| EmbedderTestContextType backend, |
| bool opengl_framebuffer) { |
| switch (backend) { |
| case EmbedderTestContextType::kVulkanContext: |
| return EmbedderTestBackingStoreProducer::RenderTargetType::kVulkanImage; |
| case EmbedderTestContextType::kOpenGLContext: |
| if (opengl_framebuffer) { |
| return EmbedderTestBackingStoreProducer::RenderTargetType:: |
| kOpenGLFramebuffer; |
| } |
| return EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture; |
| case EmbedderTestContextType::kMetalContext: |
| return EmbedderTestBackingStoreProducer::RenderTargetType::kMetalTexture; |
| case EmbedderTestContextType::kSoftwareContext: |
| return EmbedderTestBackingStoreProducer::RenderTargetType:: |
| kSoftwareBuffer; |
| } |
| } |
| |
| void ConfigureBackingStore(FlutterBackingStore& backing_store, |
| EmbedderTestContextType backend, |
| bool opengl_framebuffer) { |
| switch (backend) { |
| case EmbedderTestContextType::kVulkanContext: |
| backing_store.type = kFlutterBackingStoreTypeVulkan; |
| break; |
| case EmbedderTestContextType::kOpenGLContext: |
| if (opengl_framebuffer) { |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; |
| } else { |
| backing_store.type = kFlutterBackingStoreTypeOpenGL; |
| backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; |
| } |
| break; |
| case EmbedderTestContextType::kMetalContext: |
| backing_store.type = kFlutterBackingStoreTypeMetal; |
| break; |
| case EmbedderTestContextType::kSoftwareContext: |
| backing_store.type = kFlutterBackingStoreTypeSoftware; |
| break; |
| } |
| } |
| |
| bool WriteImageToDisk(const fml::UniqueFD& directory, |
| const std::string& name, |
| sk_sp<SkImage> image) { |
| if (!image) { |
| return false; |
| } |
| |
| auto data = image->encodeToData(); |
| |
| if (!data) { |
| return false; |
| } |
| |
| fml::NonOwnedMapping mapping(static_cast<const uint8_t*>(data->data()), |
| data->size()); |
| return WriteAtomically(directory, name.c_str(), mapping); |
| } |
| |
| bool ImageMatchesFixture(const std::string& fixture_file_name, |
| sk_sp<SkImage> scene_image) { |
| fml::FileMapping fixture_image_mapping(OpenFixture(fixture_file_name)); |
| |
| FML_CHECK(fixture_image_mapping.GetSize() != 0u) |
| << "Could not find fixture: " << fixture_file_name; |
| |
| auto encoded_image = SkData::MakeWithoutCopy( |
| fixture_image_mapping.GetMapping(), fixture_image_mapping.GetSize()); |
| auto fixture_image = |
| SkImage::MakeFromEncoded(std::move(encoded_image))->makeRasterImage(); |
| |
| FML_CHECK(fixture_image) << "Could not create image from fixture: " |
| << fixture_file_name; |
| |
| FML_CHECK(scene_image) << "Invalid scene image."; |
| |
| auto scene_image_subset = scene_image->makeSubset( |
| SkIRect::MakeWH(fixture_image->width(), fixture_image->height())); |
| |
| FML_CHECK(scene_image_subset) |
| << "Could not create image subset for fixture comparison: " |
| << scene_image_subset; |
| |
| const auto images_are_same = |
| RasterImagesAreSame(scene_image_subset, fixture_image); |
| |
| // If the images are not the same, this predicate is going to indicate test |
| // failure. Dump both the actual image and the expectation to disk to the |
| // test author can figure out what went wrong. |
| if (!images_are_same) { |
| const auto fixtures_path = GetFixturesPath(); |
| |
| const auto actual_file_name = "actual_" + fixture_file_name; |
| const auto expect_file_name = "expectation_" + fixture_file_name; |
| |
| auto fixtures_fd = OpenFixturesDirectory(); |
| |
| FML_CHECK( |
| WriteImageToDisk(fixtures_fd, actual_file_name, scene_image_subset)) |
| << "Could not write file to disk: " << actual_file_name; |
| |
| FML_CHECK(WriteImageToDisk(fixtures_fd, expect_file_name, fixture_image)) |
| << "Could not write file to disk: " << expect_file_name; |
| |
| FML_LOG(ERROR) << "Image did not match expectation." << std::endl |
| << "Expected:" |
| << fml::paths::JoinPaths({fixtures_path, expect_file_name}) |
| << std::endl |
| << "Got:" |
| << fml::paths::JoinPaths({fixtures_path, actual_file_name}) |
| << std::endl; |
| } |
| return images_are_same; |
| } |
| |
| bool ImageMatchesFixture(const std::string& fixture_file_name, |
| std::future<sk_sp<SkImage>>& scene_image) { |
| return ImageMatchesFixture(fixture_file_name, scene_image.get()); |
| } |
| |
| bool SurfacePixelDataMatchesBytes(SkSurface* surface, |
| const std::vector<uint8_t>& bytes) { |
| SkPixmap pixmap; |
| auto ok = surface->peekPixels(&pixmap); |
| if (!ok) { |
| return false; |
| } |
| |
| auto matches = (pixmap.rowBytes() == bytes.size()) && |
| (memcmp(bytes.data(), pixmap.addr(), bytes.size()) == 0); |
| |
| if (!matches) { |
| FML_LOG(ERROR) << "SkImage pixel data didn't match bytes."; |
| |
| { |
| const uint8_t* addr = (const uint8_t*)pixmap.addr(); |
| std::stringstream stream; |
| for (size_t i = 0; i < pixmap.computeByteSize(); ++i) { |
| stream << "0x" << std::setfill('0') << std::setw(2) << std::uppercase |
| << std::hex << static_cast<int>(addr[i]); |
| if (i != pixmap.computeByteSize() - 1) { |
| stream << ", "; |
| } |
| } |
| FML_LOG(ERROR) << " Actual: " << stream.str(); |
| } |
| { |
| std::stringstream stream; |
| for (auto b = bytes.begin(); b != bytes.end(); ++b) { |
| stream << "0x" << std::setfill('0') << std::setw(2) << std::uppercase |
| << std::hex << static_cast<int>(*b); |
| if (b != bytes.end() - 1) { |
| stream << ", "; |
| } |
| } |
| FML_LOG(ERROR) << " Expected: " << stream.str(); |
| } |
| } |
| |
| return matches; |
| } |
| |
| bool SurfacePixelDataMatchesBytes(std::future<SkSurface*>& surface_future, |
| const std::vector<uint8_t>& bytes) { |
| return SurfacePixelDataMatchesBytes(surface_future.get(), bytes); |
| } |
| |
| void FilterMutationsByType( |
| const FlutterPlatformViewMutation** mutations, |
| size_t count, |
| FlutterPlatformViewMutationType type, |
| std::function<void(const FlutterPlatformViewMutation& mutation)> handler) { |
| if (mutations == nullptr) { |
| return; |
| } |
| |
| for (size_t i = 0; i < count; ++i) { |
| const FlutterPlatformViewMutation* mutation = mutations[i]; |
| if (mutation->type != type) { |
| continue; |
| } |
| |
| handler(*mutation); |
| } |
| } |
| |
| void FilterMutationsByType( |
| const FlutterPlatformView* view, |
| FlutterPlatformViewMutationType type, |
| std::function<void(const FlutterPlatformViewMutation& mutation)> handler) { |
| return FilterMutationsByType(view->mutations, view->mutations_count, type, |
| handler); |
| } |
| |
| SkMatrix GetTotalMutationTransformationMatrix( |
| const FlutterPlatformViewMutation** mutations, |
| size_t count) { |
| SkMatrix collected; |
| |
| FilterMutationsByType( |
| mutations, count, kFlutterPlatformViewMutationTypeTransformation, |
| [&](const auto& mutation) { |
| collected.preConcat(SkMatrixMake(mutation.transformation)); |
| }); |
| |
| return collected; |
| } |
| |
| SkMatrix GetTotalMutationTransformationMatrix(const FlutterPlatformView* view) { |
| return GetTotalMutationTransformationMatrix(view->mutations, |
| view->mutations_count); |
| } |
| |
| } // namespace testing |
| } // namespace flutter |