Files
engine/shell/platform/android/platform_view_android.cc
T
Chinmay Garde 9eb446e0d5 Move shell to //flutter and split shell/BUILD.gn into smaller pieces for each subcomponent. (#3053)
* Namespaces have been updated to reflect the move from //flutter/sky/shell to //flutter/shell.
* shell/BUILD.gn file has been split into smaller GN files for each subcomponent of the shell (common, GPU, diagnostic, testing).
* GN dependencies have been rewritten to stop exposing common shell dependencies as public. Duplicates have also been removed.
* GPU subcomponent has been updated make it more suitable for Vulkan integration.
* The GLFW backend has been resurrected.
2016-09-23 15:33:25 -07:00

611 lines
18 KiB
C++

// Copyright 2015 The Chromium 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/android/platform_view_android.h"
#include <android/input.h>
#include <android/native_window_jni.h>
#include <EGL/egl.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <utility>
#include "base/android/jni_android.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/trace_event/trace_event.h"
#include "flutter/common/threads.h"
#include "flutter/flow/compositor_context.h"
#include "flutter/runtime/dart_service_isolate.h"
#include "flutter/shell/common/shell.h"
#include "jni/FlutterView_jni.h"
#include "third_party/skia/include/core/SkSurface.h"
namespace shell {
namespace {
template <class T>
using EGLResult = std::pair<bool, T>;
EGLDisplay g_display = EGL_NO_DISPLAY;
EGLContext g_resource_context = EGL_NO_CONTEXT;
EGLSurface g_resource_surface = EGL_NO_SURFACE;
void LogLastEGLError() {
struct EGLNameErrorPair {
const char* name;
EGLint code;
};
#define _EGL_ERROR_DESC(a) \
{ #a, a }
const EGLNameErrorPair pairs[] = {
_EGL_ERROR_DESC(EGL_SUCCESS),
_EGL_ERROR_DESC(EGL_NOT_INITIALIZED),
_EGL_ERROR_DESC(EGL_BAD_ACCESS),
_EGL_ERROR_DESC(EGL_BAD_ALLOC),
_EGL_ERROR_DESC(EGL_BAD_ATTRIBUTE),
_EGL_ERROR_DESC(EGL_BAD_CONTEXT),
_EGL_ERROR_DESC(EGL_BAD_CONFIG),
_EGL_ERROR_DESC(EGL_BAD_CURRENT_SURFACE),
_EGL_ERROR_DESC(EGL_BAD_DISPLAY),
_EGL_ERROR_DESC(EGL_BAD_SURFACE),
_EGL_ERROR_DESC(EGL_BAD_MATCH),
_EGL_ERROR_DESC(EGL_BAD_PARAMETER),
_EGL_ERROR_DESC(EGL_BAD_NATIVE_PIXMAP),
_EGL_ERROR_DESC(EGL_BAD_NATIVE_WINDOW),
_EGL_ERROR_DESC(EGL_CONTEXT_LOST),
};
#undef _EGL_ERROR_DESC
const auto count = sizeof(pairs) / sizeof(EGLNameErrorPair);
EGLint last_error = eglGetError();
for (size_t i = 0; i < count; i++) {
if (last_error == pairs[i].code) {
DLOG(INFO) << "EGL Error: " << pairs[i].name << " (" << pairs[i].code
<< ")";
return;
}
}
DLOG(WARNING) << "Unknown EGL Error";
}
EGLResult<EGLSurface> CreatePBufferSurface(EGLDisplay display,
EGLConfig config) {
// We only ever create pbuffer surfaces for background resource loading
// contexts. We never bind the pbuffer to anything.
const EGLint attribs[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};
EGLSurface surface = eglCreatePbufferSurface(display, config, attribs);
return {surface != EGL_NO_SURFACE, surface};
}
EGLResult<EGLSurface> CreateContext(EGLDisplay display,
EGLConfig config,
EGLContext share = EGL_NO_CONTEXT) {
EGLint attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
EGLContext context = eglCreateContext(display, config, share, attributes);
return {context != EGL_NO_CONTEXT, context};
}
EGLResult<EGLConfig> ChooseEGLConfiguration(
EGLDisplay display,
PlatformView::SurfaceConfig config) {
EGLint attributes[] = {
// clang-format off
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, config.red_bits,
EGL_GREEN_SIZE, config.green_bits,
EGL_BLUE_SIZE, config.blue_bits,
EGL_ALPHA_SIZE, config.alpha_bits,
EGL_DEPTH_SIZE, config.depth_bits,
EGL_STENCIL_SIZE, config.stencil_bits,
EGL_NONE, // termination sentinel
// clang-format on
};
EGLint config_count = 0;
EGLConfig egl_config = nullptr;
if (eglChooseConfig(display, attributes, &egl_config, 1, &config_count) !=
EGL_TRUE) {
return {false, nullptr};
}
bool success = config_count > 0 && egl_config != nullptr;
return {success, success ? egl_config : nullptr};
}
void InitGlobal() {
// Get the display.
g_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (g_display == EGL_NO_DISPLAY)
return;
// Initialize the display connection.
if (eglInitialize(g_display, nullptr, nullptr) != EGL_TRUE)
return;
bool success;
// Choose a config for resource loading.
PlatformView::SurfaceConfig resource_config;
resource_config.stencil_bits = 0;
EGLConfig resource_egl_config;
std::tie(success, resource_egl_config) =
ChooseEGLConfiguration(g_display, resource_config);
if (!success) {
DLOG(INFO) << "Could not choose a resource configuration.";
LogLastEGLError();
return;
}
// Create a pbuffer surface for the configuration for resource loading.
std::tie(success, g_resource_surface) =
CreatePBufferSurface(g_display, resource_egl_config);
if (!success) {
DLOG(INFO) << "Could not create the pbuffer surface for resource loading.";
LogLastEGLError();
return;
}
// Create a resource context for the configuration.
std::tie(success, g_resource_context) =
CreateContext(g_display, resource_egl_config);
if (!success) {
DLOG(INFO) << "Could not create the resource context.";
LogLastEGLError();
return;
}
}
} // namespace
class AndroidNativeWindow {
public:
using Handle = ANativeWindow*;
explicit AndroidNativeWindow(Handle window) : window_(window) {
if (window_ != nullptr) {
ANativeWindow_acquire(window_);
}
}
~AndroidNativeWindow() {
if (window_ != nullptr) {
ANativeWindow_release(window_);
window_ = nullptr;
}
}
bool IsValid() const { return window_ != nullptr; }
Handle handle() const { return window_; }
private:
Handle window_;
FTL_DISALLOW_COPY_AND_ASSIGN(AndroidNativeWindow);
};
class AndroidGLContext {
public:
explicit AndroidGLContext(AndroidNativeWindow::Handle window_handle,
PlatformView::SurfaceConfig config)
: window_(window_handle),
config_(nullptr),
surface_(EGL_NO_SURFACE),
context_(EGL_NO_CONTEXT),
valid_(false) {
if (!window_.IsValid()) {
// We always require a valid window since we are only going to deal
// with window surfaces.
return;
}
bool success = false;
// Choose a valid configuration.
std::tie(success, config_) = ChooseEGLConfiguration(g_display, config);
if (!success) {
DLOG(INFO) << "Could not choose a window configuration.";
LogLastEGLError();
return;
}
// Create a window surface for the configuration.
std::tie(success, surface_) =
CreateWindowSurface(g_display, config_, window_.handle());
if (!success) {
DLOG(INFO) << "Could not create the window surface.";
LogLastEGLError();
return;
}
// Create a context for the configuration.
std::tie(success, context_) =
CreateContext(g_display, config_, g_resource_context);
if (!success) {
DLOG(INFO) << "Could not create the main rendering context";
LogLastEGLError();
return;
}
// All done!
valid_ = true;
}
~AndroidGLContext() {
if (!TeardownContext(g_display, context_)) {
LOG(INFO)
<< "Could not tear down the EGL context. Possible resource leak.";
LogLastEGLError();
}
if (!TeardownSurface(g_display, surface_)) {
LOG(INFO)
<< "Could not tear down the EGL surface. Possible resource leak.";
LogLastEGLError();
}
}
bool IsValid() const { return valid_; }
bool ContextMakeCurrent() {
if (eglMakeCurrent(g_display, surface_, surface_, context_) != EGL_TRUE) {
LOG(INFO) << "Could not make the context current";
LogLastEGLError();
return false;
}
return true;
}
bool SwapBuffers() { return eglSwapBuffers(g_display, surface_); }
SkISize GetSize() {
EGLint width = 0;
EGLint height = 0;
if (!eglQuerySurface(g_display, surface_, EGL_WIDTH, &width) ||
!eglQuerySurface(g_display, surface_, EGL_HEIGHT, &height)) {
LOG(ERROR) << "Unable to query EGL surface size";
LogLastEGLError();
return SkISize::Make(0, 0);
}
return SkISize::Make(width, height);
}
void Resize(const SkISize& size) {
eglMakeCurrent(g_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
TeardownSurface(g_display, surface_);
bool success;
std::tie(success, surface_) =
CreateWindowSurface(g_display, config_, window_.handle());
if (!success) {
LOG(ERROR) << "Unable to create EGL window surface";
}
}
private:
AndroidNativeWindow window_;
EGLConfig config_;
EGLSurface surface_;
EGLContext context_;
bool valid_;
static bool TeardownContext(EGLDisplay display, EGLContext context) {
if (context != EGL_NO_CONTEXT) {
return eglDestroyContext(display, context) == EGL_TRUE;
}
return true;
}
static bool TeardownSurface(EGLDisplay display, EGLSurface surface) {
if (surface != EGL_NO_SURFACE) {
return eglDestroySurface(display, surface) == EGL_TRUE;
}
return true;
}
static EGLResult<EGLSurface> CreateWindowSurface(
EGLDisplay display,
EGLConfig config,
AndroidNativeWindow::Handle window_handle) {
// The configurations are only required when dealing with extensions or VG.
// We do neither.
EGLSurface surface = eglCreateWindowSurface(
display, config, reinterpret_cast<EGLNativeWindowType>(window_handle),
nullptr);
return {surface != EGL_NO_SURFACE, surface};
}
FTL_DISALLOW_COPY_AND_ASSIGN(AndroidGLContext);
};
static jlong Attach(JNIEnv* env,
jclass clazz,
jint skyEngineHandle,
jobject flutterView) {
PlatformViewAndroid* view = new PlatformViewAndroid();
view->ConnectToEngine(mojo::InterfaceRequest<sky::SkyEngine>(
mojo::ScopedMessagePipeHandle(mojo::MessagePipeHandle(skyEngineHandle))));
// Create a weak reference to the flutterView Java object so that we can make
// calls into it later.
view->set_flutter_view(JavaObjectWeakGlobalRef(env, flutterView));
return reinterpret_cast<jlong>(view);
}
jint GetObservatoryPort(JNIEnv* env, jclass clazz) {
return blink::DartServiceIsolate::GetObservatoryPort();
}
// static
bool PlatformViewAndroid::Register(JNIEnv* env) {
return RegisterNativesImpl(env);
}
PlatformViewAndroid::PlatformViewAndroid() : weak_factory_(this) {
// If this is the first PlatformView, then intiialize EGL and set up
// the resource context.
if (g_display == EGL_NO_DISPLAY)
InitGlobal();
}
PlatformViewAndroid::~PlatformViewAndroid() = default;
void PlatformViewAndroid::Detach(JNIEnv* env, jobject obj) {
ReleaseSurface();
delete this;
}
void PlatformViewAndroid::SurfaceCreated(JNIEnv* env,
jobject obj,
jobject jsurface) {
// Note: This ensures that any local references used by
// ANativeWindow_fromSurface are released immediately. This is needed as a
// workaround for https://code.google.com/p/android/issues/detail?id=68174
{
base::android::ScopedJavaLocalFrame scoped_local_reference_frame(env);
ANativeWindow* window = ANativeWindow_fromSurface(env, jsurface);
std::unique_ptr<AndroidGLContext> context(
new AndroidGLContext(window, surface_config_));
if (context->IsValid()) {
context_ = std::move(context);
}
ANativeWindow_release(window);
}
}
void PlatformViewAndroid::SurfaceChanged(JNIEnv* env,
jobject obj,
jint backgroundColor) {
NotifyCreated(
[this, backgroundColor] { config_.rasterizer->Clear(backgroundColor); });
SetupResourceContextOnIOThread();
UpdateThreadPriorities();
}
void PlatformViewAndroid::UpdateThreadPriorities() {
blink::Threads::Gpu()->PostTask(
[]() { ::setpriority(PRIO_PROCESS, gettid(), -2); });
blink::Threads::UI()->PostTask(
[]() { ::setpriority(PRIO_PROCESS, gettid(), -1); });
}
void PlatformViewAndroid::SurfaceDestroyed(JNIEnv* env, jobject obj) {
ReleaseSurface();
}
void PlatformViewAndroid::ReleaseSurface() {
if (context_) {
NotifyDestroyed();
context_ = nullptr;
}
}
ftl::WeakPtr<shell::PlatformView> PlatformViewAndroid::GetWeakViewPtr() {
return weak_factory_.GetWeakPtr();
}
uint64_t PlatformViewAndroid::DefaultFramebuffer() const {
// FBO 0 is the default window bound framebuffer on Android.
return 0;
}
bool PlatformViewAndroid::ContextMakeCurrent() {
return context_ != nullptr ? context_->ContextMakeCurrent() : false;
}
bool PlatformViewAndroid::ResourceContextMakeCurrent() {
if (eglMakeCurrent(g_display, g_resource_surface, g_resource_surface,
g_resource_context) != EGL_TRUE) {
LOG(INFO) << "Could not make the resource context current";
LogLastEGLError();
return false;
}
return true;
}
bool PlatformViewAndroid::SwapBuffers() {
TRACE_EVENT0("flutter", "PlatformViewAndroid::SwapBuffers");
return context_ != nullptr ? context_->SwapBuffers() : false;
}
SkISize PlatformViewAndroid::GetSize() {
return context_->GetSize();
}
void PlatformViewAndroid::Resize(const SkISize& size) {
context_->Resize(size);
}
void PlatformViewAndroid::RunFromSource(const std::string& main,
const std::string& packages,
const std::string& assets_directory) {
FTL_CHECK(base::android::IsVMInitialized());
JNIEnv* env = base::android::AttachCurrentThread();
FTL_CHECK(env);
{
base::android::ScopedJavaLocalRef<jobject> local_flutter_view =
flutter_view_.get(env);
if (local_flutter_view.is_null()) {
// Collected.
return;
}
// Grab the class of the flutter view.
jclass flutter_view_class = env->GetObjectClass(local_flutter_view.obj());
FTL_CHECK(flutter_view_class);
// Grab the runFromSource method id.
jmethodID run_from_source_method_id = env->GetMethodID(
flutter_view_class, "runFromSource",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
FTL_CHECK(run_from_source_method_id);
// Invoke runFromSource on the Android UI thread.
jstring java_main = env->NewStringUTF(main.c_str());
FTL_CHECK(java_main);
jstring java_packages = env->NewStringUTF(packages.c_str());
FTL_CHECK(java_packages);
jstring java_assets_directory = env->NewStringUTF(assets_directory.c_str());
FTL_CHECK(java_assets_directory);
env->CallVoidMethod(local_flutter_view.obj(), run_from_source_method_id,
java_main, java_packages, java_assets_directory);
}
// Detaching from the VM deletes any stray local references.
base::android::DetachFromVM();
}
base::android::ScopedJavaLocalRef<jobject> PlatformViewAndroid::GetBitmap(
JNIEnv* env,
jobject obj) {
// Render the last frame to an array of pixels on the GPU thread.
// The pixels will be returned as a global JNI reference to an int array.
ftl::AutoResetWaitableEvent latch;
jobject pixels_ref = nullptr;
SkISize frame_size;
blink::Threads::Gpu()->PostTask([this, &latch, &pixels_ref, &frame_size]() {
GetBitmapGpuTask(&latch, &pixels_ref, &frame_size);
});
latch.Wait();
// Convert the pixel array to an Android bitmap.
if (pixels_ref == nullptr)
return base::android::ScopedJavaLocalRef<jobject>();
base::android::ScopedJavaGlobalRef<jobject> pixels(env, pixels_ref);
jclass bitmap_class = env->FindClass("android/graphics/Bitmap");
FTL_CHECK(bitmap_class);
jmethodID create_bitmap = env->GetStaticMethodID(
bitmap_class, "createBitmap",
"([IIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
FTL_CHECK(create_bitmap);
jclass bitmap_config_class = env->FindClass("android/graphics/Bitmap$Config");
FTL_CHECK(bitmap_config_class);
jmethodID bitmap_config_value_of = env->GetStaticMethodID(
bitmap_config_class, "valueOf",
"(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");
FTL_CHECK(bitmap_config_value_of);
jstring argb = env->NewStringUTF("ARGB_8888");
FTL_CHECK(argb);
jobject bitmap_config = env->CallStaticObjectMethod(
bitmap_config_class, bitmap_config_value_of, argb);
FTL_CHECK(bitmap_config);
jobject bitmap = env->CallStaticObjectMethod(
bitmap_class, create_bitmap, pixels.obj(), frame_size.width(),
frame_size.height(), bitmap_config);
return base::android::ScopedJavaLocalRef<jobject>(env, bitmap);
}
void PlatformViewAndroid::GetBitmapGpuTask(ftl::AutoResetWaitableEvent* latch,
jobject* pixels_out,
SkISize* size_out) {
flow::LayerTree* layer_tree = rasterizer_->GetLastLayerTree();
if (layer_tree == nullptr)
return;
JNIEnv* env = base::android::AttachCurrentThread();
FTL_CHECK(env);
const SkISize& frame_size = layer_tree->frame_size();
jsize pixels_size = frame_size.width() * frame_size.height();
jintArray pixels_array = env->NewIntArray(pixels_size);
FTL_CHECK(pixels_array);
jint* pixels = env->GetIntArrayElements(pixels_array, nullptr);
FTL_CHECK(pixels);
SkImageInfo image_info =
SkImageInfo::Make(frame_size.width(), frame_size.height(),
kRGBA_8888_SkColorType, kPremul_SkAlphaType);
sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect(
image_info, pixels, frame_size.width() * sizeof(jint));
flow::CompositorContext compositor_context;
SkCanvas* canvas = surface->getCanvas();
flow::CompositorContext::ScopedFrame frame =
compositor_context.AcquireFrame(nullptr, *canvas, false);
canvas->clear(SK_ColorBLACK);
layer_tree->Raster(frame);
canvas->flush();
// Our configuration of Skia does not support rendering to the
// BitmapConfig.ARGB_8888 format expected by android.graphics.Bitmap.
// Convert from kRGBA_8888 to kBGRA_8888 (equivalent to ARGB_8888).
for (int i = 0; i < pixels_size; i++) {
uint8_t* bytes = reinterpret_cast<uint8_t*>(pixels + i);
std::swap(bytes[0], bytes[2]);
}
env->ReleaseIntArrayElements(pixels_array, pixels, 0);
*pixels_out = env->NewGlobalRef(pixels_array);
*size_out = frame_size;
base::android::DetachFromVM();
latch->Signal();
}
} // namespace shell