// 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. #include "flutter/shell/platform/embedder/tests/embedder_test_compositor.h" #include "flutter/fml/logging.h" #include "flutter/shell/platform/embedder/tests/embedder_assertions.h" #include "third_party/skia/include/core/SkSurface.h" namespace flutter { namespace testing { EmbedderTestCompositor::EmbedderTestCompositor(sk_sp context) : context_(context) { FML_CHECK(context_); } EmbedderTestCompositor::~EmbedderTestCompositor() = default; void EmbedderTestCompositor::SetRenderTargetType(RenderTargetType type) { type_ = type; } bool EmbedderTestCompositor::CreateBackingStore( const FlutterBackingStoreConfig* config, FlutterBackingStore* backing_store_out) { bool success = false; switch (type_) { case RenderTargetType::kOpenGLFramebuffer: success = CreateFramebufferRenderSurface(config, backing_store_out); break; case RenderTargetType::kOpenGLTexture: success = CreateTextureRenderSurface(config, backing_store_out); break; case RenderTargetType::kSoftwareBuffer: success = CreateSoftwareRenderSurface(config, backing_store_out); break; default: FML_CHECK(false); return false; } if (success) { backing_stores_count_++; } return success; } bool EmbedderTestCompositor::CollectBackingStore( const FlutterBackingStore* backing_store) { // We have already set the destruction callback for the various backing // stores. Our user_data is just the canvas from that backing store and does // not need to be explicitly collected. Embedders might have some other state // they want to collect though. backing_stores_count_--; return true; } bool EmbedderTestCompositor::UpdateOffscrenComposition( const FlutterLayer** layers, size_t layers_count) { last_composition_ = nullptr; auto surface_size = SkISize::Make(800, 600); const auto image_info = SkImageInfo::MakeN32Premul(surface_size); auto surface = type_ == RenderTargetType::kSoftwareBuffer ? SkSurface::MakeRaster(image_info) : SkSurface::MakeRenderTarget( context_.get(), // context SkBudgeted::kNo, // budgeted image_info, // image info 1, // sample count kTopLeft_GrSurfaceOrigin, // surface origin nullptr, // surface properties false // create mipmaps ); if (!surface) { FML_LOG(ERROR) << "Could not update the off-screen composition."; return false; } auto canvas = surface->getCanvas(); // This has to be transparent because we are going to be compositing this // sub-hierarchy onto the on-screen surface. canvas->clear(SK_ColorTRANSPARENT); for (size_t i = 0; i < layers_count; ++i) { const auto* layer = layers[i]; sk_sp platform_renderered_contents; sk_sp layer_image; SkIPoint canvas_offset = SkIPoint::Make(0, 0); switch (layer->type) { case kFlutterLayerContentTypeBackingStore: layer_image = reinterpret_cast(layer->backing_store->user_data) ->makeImageSnapshot(); break; case kFlutterLayerContentTypePlatformView: layer_image = platform_view_renderer_callback_ ? platform_view_renderer_callback_(*layer, context_.get()) : nullptr; canvas_offset = SkIPoint::Make(layer->offset.x, layer->offset.y); break; }; if (!layer_image && layer->type != kFlutterLayerContentTypePlatformView) { FML_LOG(ERROR) << "Could not snapshot layer in test compositor: " << *layer; return false; } // The image rendered by Flutter already has the correct offset and // transformation applied. The layers offset is meant for the platform. canvas->drawImage(layer_image.get(), canvas_offset.x(), canvas_offset.y()); } last_composition_ = surface->makeImageSnapshot(); if (!last_composition_) { FML_LOG(ERROR) << "Could not update the contents of the sub-composition."; return false; } if (next_scene_callback_) { auto last_composition_snapshot = last_composition_->makeRasterImage(); FML_CHECK(last_composition_snapshot); auto callback = next_scene_callback_; next_scene_callback_ = nullptr; callback(std::move(last_composition_snapshot)); } return true; } sk_sp EmbedderTestCompositor::GetLastComposition() { return last_composition_; } bool EmbedderTestCompositor::Present(const FlutterLayer** layers, size_t layers_count) { if (!UpdateOffscrenComposition(layers, layers_count)) { FML_LOG(ERROR) << "Could not update the off-screen composition in the test compositor"; return false; } // If the test has asked to access the layers and renderers being presented. // Access the same and present it to the test for its test assertions. if (next_present_callback_) { auto callback = next_present_callback_; next_present_callback_ = nullptr; callback(layers, layers_count); } return true; } bool EmbedderTestCompositor::CreateFramebufferRenderSurface( const FlutterBackingStoreConfig* config, FlutterBackingStore* backing_store_out) { const auto image_info = SkImageInfo::MakeN32Premul(config->size.width, config->size.height); auto surface = SkSurface::MakeRenderTarget(context_.get(), // context SkBudgeted::kNo, // budgeted image_info, // image info 1, // sample count kTopLeft_GrSurfaceOrigin, // surface origin nullptr, // surface properties false // mipmaps ); if (!surface) { FML_LOG(ERROR) << "Could not create render target for compositor layer."; return false; } GrBackendRenderTarget render_target = surface->getBackendRenderTarget( SkSurface::BackendHandleAccess::kDiscardWrite_BackendHandleAccess); if (!render_target.isValid()) { FML_LOG(ERROR) << "Backend render target was invalid."; return false; } GrGLFramebufferInfo framebuffer_info = {}; if (!render_target.getGLFramebufferInfo(&framebuffer_info)) { FML_LOG(ERROR) << "Could not access backend framebuffer info."; return false; } backing_store_out->type = kFlutterBackingStoreTypeOpenGL; backing_store_out->user_data = surface.get(); backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; backing_store_out->open_gl.framebuffer.target = framebuffer_info.fFormat; backing_store_out->open_gl.framebuffer.name = framebuffer_info.fFBOID; // The balancing unref is in the destruction callback. surface->ref(); backing_store_out->open_gl.framebuffer.user_data = surface.get(); backing_store_out->open_gl.framebuffer.destruction_callback = [](void* user_data) { reinterpret_cast(user_data)->unref(); }; return true; } bool EmbedderTestCompositor::CreateTextureRenderSurface( const FlutterBackingStoreConfig* config, FlutterBackingStore* backing_store_out) { const auto image_info = SkImageInfo::MakeN32Premul(config->size.width, config->size.height); auto surface = SkSurface::MakeRenderTarget(context_.get(), // context SkBudgeted::kNo, // budgeted image_info, // image info 1, // sample count kTopLeft_GrSurfaceOrigin, // surface origin nullptr, // surface properties false // mipmaps ); if (!surface) { FML_LOG(ERROR) << "Could not create render target for compositor layer."; return false; } GrBackendTexture render_texture = surface->getBackendTexture( SkSurface::BackendHandleAccess::kDiscardWrite_BackendHandleAccess); if (!render_texture.isValid()) { FML_LOG(ERROR) << "Backend render texture was invalid."; return false; } GrGLTextureInfo texture_info = {}; if (!render_texture.getGLTextureInfo(&texture_info)) { FML_LOG(ERROR) << "Could not access backend texture info."; return false; } backing_store_out->type = kFlutterBackingStoreTypeOpenGL; backing_store_out->user_data = surface.get(); backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeTexture; backing_store_out->open_gl.texture.target = texture_info.fTarget; backing_store_out->open_gl.texture.name = texture_info.fID; backing_store_out->open_gl.texture.format = texture_info.fFormat; // The balancing unref is in the destruction callback. surface->ref(); backing_store_out->open_gl.texture.user_data = surface.get(); backing_store_out->open_gl.texture.destruction_callback = [](void* user_data) { reinterpret_cast(user_data)->unref(); }; return true; } bool EmbedderTestCompositor::CreateSoftwareRenderSurface( const FlutterBackingStoreConfig* config, FlutterBackingStore* backing_store_out) { auto surface = SkSurface::MakeRaster( SkImageInfo::MakeN32Premul(config->size.width, config->size.height)); if (!surface) { FML_LOG(ERROR) << "Could not create the render target for compositor layer."; return false; } SkPixmap pixmap; if (!surface->peekPixels(&pixmap)) { FML_LOG(ERROR) << "Could not peek pixels of pixmap."; return false; } backing_store_out->type = kFlutterBackingStoreTypeSoftware; backing_store_out->user_data = surface.get(); backing_store_out->software.allocation = pixmap.addr(); backing_store_out->software.row_bytes = pixmap.rowBytes(); backing_store_out->software.height = pixmap.height(); // The balancing unref is in the destruction callback. surface->ref(); backing_store_out->software.user_data = surface.get(); backing_store_out->software.destruction_callback = [](void* user_data) { reinterpret_cast(user_data)->unref(); }; return true; } void EmbedderTestCompositor::SetNextPresentCallback( PresentCallback next_present_callback) { FML_CHECK(!next_present_callback_); next_present_callback_ = next_present_callback; } void EmbedderTestCompositor::SetNextSceneCallback( NextSceneCallback next_scene_callback) { FML_CHECK(!next_scene_callback_); next_scene_callback_ = next_scene_callback; } void EmbedderTestCompositor::SetPlatformViewRendererCallback( PlatformViewRendererCallback callback) { platform_view_renderer_callback_ = callback; } size_t EmbedderTestCompositor::GetBackingStoresCount() const { return backing_stores_count_; } } // namespace testing } // namespace flutter