blob: 843132643b4b2db99b167644a8923e1155c5de50 [file] [log] [blame]
// 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(SkISize surface_size,
sk_sp<GrContext> context)
: surface_size_(surface_size), context_(context) {
FML_CHECK(!surface_size_.isEmpty()) << "Surface size must not be empty";
FML_CHECK(context_);
}
EmbedderTestCompositor::~EmbedderTestCompositor() = default;
void EmbedderTestCompositor::SetRenderTargetType(RenderTargetType type) {
type_ = type;
}
static void InvokeAllCallbacks(const std::vector<fml::closure>& callbacks) {
for (const auto& callback : callbacks) {
if (callback) {
callback();
}
}
}
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_created_++;
InvokeAllCallbacks(on_create_render_target_callbacks_);
}
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_collected_++;
InvokeAllCallbacks(on_collect_render_target_callbacks_);
return true;
}
bool EmbedderTestCompositor::UpdateOffscrenComposition(
const FlutterLayer** layers,
size_t layers_count) {
last_composition_ = nullptr;
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<SkImage> platform_renderered_contents;
sk_sp<SkImage> layer_image;
SkIPoint canvas_offset = SkIPoint::Make(0, 0);
switch (layer->type) {
case kFlutterLayerContentTypeBackingStore:
layer_image =
reinterpret_cast<SkSurface*>(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 the layer is not a platform view but the engine did not specify an
// image for the backing store, it is an error.
if (!layer_image && layer->type != kFlutterLayerContentTypePlatformView) {
FML_LOG(ERROR) << "Could not snapshot layer in test compositor: "
<< *layer;
return false;
}
// The test could have just specified no contents to be rendered in place of
// a platform view. This is not an error.
if (layer_image) {
// 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<SkImage> 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 (present_callback_) {
auto callback = present_callback_;
if (present_callback_is_one_shot_) {
present_callback_ = nullptr;
}
callback(layers, layers_count);
}
InvokeAllCallbacks(on_present_callbacks_);
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
kBottomLeft_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<SkSurface*>(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
kBottomLeft_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<SkSurface*>(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<SkSurface*>(user_data)->unref();
};
return true;
}
void EmbedderTestCompositor::SetNextPresentCallback(
const PresentCallback& next_present_callback) {
SetPresentCallback(next_present_callback, true);
}
void EmbedderTestCompositor::SetPresentCallback(
const PresentCallback& present_callback,
bool one_shot) {
FML_CHECK(!present_callback_);
present_callback_ = present_callback;
present_callback_is_one_shot_ = one_shot;
}
void EmbedderTestCompositor::SetNextSceneCallback(
const NextSceneCallback& next_scene_callback) {
FML_CHECK(!next_scene_callback_);
next_scene_callback_ = next_scene_callback;
}
void EmbedderTestCompositor::SetPlatformViewRendererCallback(
const PlatformViewRendererCallback& callback) {
platform_view_renderer_callback_ = callback;
}
size_t EmbedderTestCompositor::GetPendingBackingStoresCount() const {
FML_CHECK(backing_stores_created_ >= backing_stores_collected_);
return backing_stores_created_ - backing_stores_collected_;
}
size_t EmbedderTestCompositor::GetBackingStoresCreatedCount() const {
return backing_stores_created_;
}
size_t EmbedderTestCompositor::GetBackingStoresCollectedCount() const {
return backing_stores_collected_;
}
void EmbedderTestCompositor::AddOnCreateRenderTargetCallback(
fml::closure callback) {
on_create_render_target_callbacks_.push_back(callback);
}
void EmbedderTestCompositor::AddOnCollectRenderTargetCallback(
fml::closure callback) {
on_collect_render_target_callbacks_.push_back(callback);
}
void EmbedderTestCompositor::AddOnPresentCallback(fml::closure callback) {
on_present_callbacks_.push_back(callback);
}
} // namespace testing
} // namespace flutter