|  | // 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/testing/test_gl_surface.h" | 
|  |  | 
|  | #include <EGL/egl.h> | 
|  | #include <EGL/eglext.h> | 
|  | #include <EGL/eglplatform.h> | 
|  | #include <GLES2/gl2.h> | 
|  |  | 
|  | #include <sstream> | 
|  | #include <string> | 
|  |  | 
|  | #include "flutter/fml/build_config.h" | 
|  | #include "flutter/fml/logging.h" | 
|  | #include "third_party/skia/include/core/SkColorSpace.h" | 
|  | #include "third_party/skia/include/core/SkColorType.h" | 
|  | #include "third_party/skia/include/core/SkSurface.h" | 
|  | #include "third_party/skia/include/gpu/GrBackendSurface.h" | 
|  | #include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" | 
|  | #include "third_party/skia/include/gpu/ganesh/gl/GrGLBackendSurface.h" | 
|  | #include "third_party/skia/include/gpu/gl/GrGLAssembleInterface.h" | 
|  | #include "third_party/skia/include/gpu/gl/GrGLTypes.h" | 
|  |  | 
|  | namespace flutter { | 
|  | namespace testing { | 
|  |  | 
|  | static std::string GetEGLError() { | 
|  | std::stringstream stream; | 
|  |  | 
|  | auto error = ::eglGetError(); | 
|  |  | 
|  | stream << "EGL Result: '"; | 
|  |  | 
|  | switch (error) { | 
|  | case EGL_SUCCESS: | 
|  | stream << "EGL_SUCCESS"; | 
|  | break; | 
|  | case EGL_NOT_INITIALIZED: | 
|  | stream << "EGL_NOT_INITIALIZED"; | 
|  | break; | 
|  | case EGL_BAD_ACCESS: | 
|  | stream << "EGL_BAD_ACCESS"; | 
|  | break; | 
|  | case EGL_BAD_ALLOC: | 
|  | stream << "EGL_BAD_ALLOC"; | 
|  | break; | 
|  | case EGL_BAD_ATTRIBUTE: | 
|  | stream << "EGL_BAD_ATTRIBUTE"; | 
|  | break; | 
|  | case EGL_BAD_CONTEXT: | 
|  | stream << "EGL_BAD_CONTEXT"; | 
|  | break; | 
|  | case EGL_BAD_CONFIG: | 
|  | stream << "EGL_BAD_CONFIG"; | 
|  | break; | 
|  | case EGL_BAD_CURRENT_SURFACE: | 
|  | stream << "EGL_BAD_CURRENT_SURFACE"; | 
|  | break; | 
|  | case EGL_BAD_DISPLAY: | 
|  | stream << "EGL_BAD_DISPLAY"; | 
|  | break; | 
|  | case EGL_BAD_SURFACE: | 
|  | stream << "EGL_BAD_SURFACE"; | 
|  | break; | 
|  | case EGL_BAD_MATCH: | 
|  | stream << "EGL_BAD_MATCH"; | 
|  | break; | 
|  | case EGL_BAD_PARAMETER: | 
|  | stream << "EGL_BAD_PARAMETER"; | 
|  | break; | 
|  | case EGL_BAD_NATIVE_PIXMAP: | 
|  | stream << "EGL_BAD_NATIVE_PIXMAP"; | 
|  | break; | 
|  | case EGL_BAD_NATIVE_WINDOW: | 
|  | stream << "EGL_BAD_NATIVE_WINDOW"; | 
|  | break; | 
|  | case EGL_CONTEXT_LOST: | 
|  | stream << "EGL_CONTEXT_LOST"; | 
|  | break; | 
|  | default: | 
|  | stream << "Unknown"; | 
|  | } | 
|  |  | 
|  | stream << "' (0x" << std::hex << error << std::dec << ")."; | 
|  | return stream.str(); | 
|  | } | 
|  |  | 
|  | static bool HasExtension(const char* extensions, const char* name) { | 
|  | const char* r = strstr(extensions, name); | 
|  | auto len = strlen(name); | 
|  | // check that the extension name is terminated by space or null terminator | 
|  | return r != nullptr && (r[len] == ' ' || r[len] == 0); | 
|  | } | 
|  |  | 
|  | static void CheckSwanglekExtensions() { | 
|  | const char* extensions = ::eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); | 
|  | FML_CHECK(HasExtension(extensions, "EGL_EXT_platform_base")) << extensions; | 
|  | FML_CHECK(HasExtension(extensions, "EGL_ANGLE_platform_angle_vulkan")) | 
|  | << extensions; | 
|  | FML_CHECK(HasExtension(extensions, | 
|  | "EGL_ANGLE_platform_angle_device_type_swiftshader")) | 
|  | << extensions; | 
|  | } | 
|  |  | 
|  | static EGLDisplay CreateSwangleDisplay() { | 
|  | CheckSwanglekExtensions(); | 
|  |  | 
|  | PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_EXT = | 
|  | reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>( | 
|  | eglGetProcAddress("eglGetPlatformDisplayEXT")); | 
|  | FML_CHECK(egl_get_platform_display_EXT) | 
|  | << "eglGetPlatformDisplayEXT not available."; | 
|  |  | 
|  | const EGLint display_config[] = { | 
|  | EGL_PLATFORM_ANGLE_TYPE_ANGLE, | 
|  | EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE, | 
|  | EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, | 
|  | EGL_PLATFORM_ANGLE_DEVICE_TYPE_SWIFTSHADER_ANGLE, | 
|  | EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, | 
|  | EGL_PLATFORM_VULKAN_DISPLAY_MODE_HEADLESS_ANGLE, | 
|  | EGL_NONE, | 
|  | }; | 
|  |  | 
|  | return egl_get_platform_display_EXT( | 
|  | EGL_PLATFORM_ANGLE_ANGLE, | 
|  | reinterpret_cast<EGLNativeDisplayType*>(EGL_DEFAULT_DISPLAY), | 
|  | display_config); | 
|  | } | 
|  |  | 
|  | TestGLSurface::TestGLSurface(SkISize surface_size) | 
|  | : surface_size_(surface_size) { | 
|  | display_ = CreateSwangleDisplay(); | 
|  | FML_CHECK(display_ != EGL_NO_DISPLAY); | 
|  |  | 
|  | auto result = ::eglInitialize(display_, nullptr, nullptr); | 
|  | FML_CHECK(result == EGL_TRUE) << GetEGLError(); | 
|  |  | 
|  | EGLConfig config = {0}; | 
|  |  | 
|  | EGLint num_config = 0; | 
|  | const EGLint attribute_list[] = {EGL_RED_SIZE, | 
|  | 8, | 
|  | EGL_GREEN_SIZE, | 
|  | 8, | 
|  | EGL_BLUE_SIZE, | 
|  | 8, | 
|  | EGL_ALPHA_SIZE, | 
|  | 8, | 
|  | EGL_SURFACE_TYPE, | 
|  | EGL_PBUFFER_BIT, | 
|  | EGL_CONFORMANT, | 
|  | EGL_OPENGL_ES2_BIT, | 
|  | EGL_RENDERABLE_TYPE, | 
|  | EGL_OPENGL_ES2_BIT, | 
|  | EGL_NONE}; | 
|  |  | 
|  | result = ::eglChooseConfig(display_, attribute_list, &config, 1, &num_config); | 
|  | FML_CHECK(result == EGL_TRUE) << GetEGLError(); | 
|  | FML_CHECK(num_config == 1) << GetEGLError(); | 
|  |  | 
|  | { | 
|  | const EGLint onscreen_surface_attributes[] = { | 
|  | EGL_WIDTH,  surface_size_.width(),   // | 
|  | EGL_HEIGHT, surface_size_.height(),  // | 
|  | EGL_NONE, | 
|  | }; | 
|  |  | 
|  | onscreen_surface_ = ::eglCreatePbufferSurface( | 
|  | display_,                    // display connection | 
|  | config,                      // config | 
|  | onscreen_surface_attributes  // surface attributes | 
|  | ); | 
|  | FML_CHECK(onscreen_surface_ != EGL_NO_SURFACE) << GetEGLError(); | 
|  | } | 
|  |  | 
|  | { | 
|  | const EGLint offscreen_surface_attributes[] = { | 
|  | EGL_WIDTH,  1,  // | 
|  | EGL_HEIGHT, 1,  // | 
|  | EGL_NONE, | 
|  | }; | 
|  | offscreen_surface_ = ::eglCreatePbufferSurface( | 
|  | display_,                     // display connection | 
|  | config,                       // config | 
|  | offscreen_surface_attributes  // surface attributes | 
|  | ); | 
|  | FML_CHECK(offscreen_surface_ != EGL_NO_SURFACE) << GetEGLError(); | 
|  | } | 
|  |  | 
|  | { | 
|  | const EGLint context_attributes[] = { | 
|  | EGL_CONTEXT_CLIENT_VERSION,  // | 
|  | 2,                           // | 
|  | EGL_NONE                     // | 
|  | }; | 
|  |  | 
|  | onscreen_context_ = | 
|  | ::eglCreateContext(display_,           // display connection | 
|  | config,             // config | 
|  | EGL_NO_CONTEXT,     // sharegroup | 
|  | context_attributes  // context attributes | 
|  | ); | 
|  | FML_CHECK(onscreen_context_ != EGL_NO_CONTEXT) << GetEGLError(); | 
|  |  | 
|  | offscreen_context_ = | 
|  | ::eglCreateContext(display_,           // display connection | 
|  | config,             // config | 
|  | onscreen_context_,  // sharegroup | 
|  | context_attributes  // context attributes | 
|  | ); | 
|  | FML_CHECK(offscreen_context_ != EGL_NO_CONTEXT) << GetEGLError(); | 
|  | } | 
|  | } | 
|  |  | 
|  | TestGLSurface::~TestGLSurface() { | 
|  | context_ = nullptr; | 
|  |  | 
|  | auto result = ::eglDestroyContext(display_, onscreen_context_); | 
|  | FML_CHECK(result == EGL_TRUE) << GetEGLError(); | 
|  |  | 
|  | result = ::eglDestroyContext(display_, offscreen_context_); | 
|  | FML_CHECK(result == EGL_TRUE) << GetEGLError(); | 
|  |  | 
|  | result = ::eglDestroySurface(display_, onscreen_surface_); | 
|  | FML_CHECK(result == EGL_TRUE) << GetEGLError(); | 
|  |  | 
|  | result = ::eglDestroySurface(display_, offscreen_surface_); | 
|  | FML_CHECK(result == EGL_TRUE) << GetEGLError(); | 
|  |  | 
|  | result = ::eglTerminate(display_); | 
|  | FML_CHECK(result == EGL_TRUE); | 
|  | } | 
|  |  | 
|  | const SkISize& TestGLSurface::GetSurfaceSize() const { | 
|  | return surface_size_; | 
|  | } | 
|  |  | 
|  | bool TestGLSurface::MakeCurrent() { | 
|  | auto result = ::eglMakeCurrent(display_, onscreen_surface_, onscreen_surface_, | 
|  | onscreen_context_); | 
|  |  | 
|  | if (result == EGL_FALSE) { | 
|  | FML_LOG(ERROR) << "Could not make the context current. " << GetEGLError(); | 
|  | } | 
|  |  | 
|  | return result == EGL_TRUE; | 
|  | } | 
|  |  | 
|  | bool TestGLSurface::ClearCurrent() { | 
|  | auto result = ::eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, | 
|  | EGL_NO_CONTEXT); | 
|  |  | 
|  | if (result == EGL_FALSE) { | 
|  | FML_LOG(ERROR) << "Could not clear the current context. " << GetEGLError(); | 
|  | } | 
|  |  | 
|  | return result == EGL_TRUE; | 
|  | } | 
|  |  | 
|  | bool TestGLSurface::Present() { | 
|  | auto result = ::eglSwapBuffers(display_, onscreen_surface_); | 
|  |  | 
|  | if (result == EGL_FALSE) { | 
|  | FML_LOG(ERROR) << "Could not swap buffers. " << GetEGLError(); | 
|  | } | 
|  |  | 
|  | return result == EGL_TRUE; | 
|  | } | 
|  |  | 
|  | uint32_t TestGLSurface::GetFramebuffer(uint32_t width, uint32_t height) const { | 
|  | return GetWindowFBOId(); | 
|  | } | 
|  |  | 
|  | bool TestGLSurface::MakeResourceCurrent() { | 
|  | auto result = ::eglMakeCurrent(display_, offscreen_surface_, | 
|  | offscreen_surface_, offscreen_context_); | 
|  |  | 
|  | if (result == EGL_FALSE) { | 
|  | FML_LOG(ERROR) << "Could not make the resource context current. " | 
|  | << GetEGLError(); | 
|  | } | 
|  |  | 
|  | return result == EGL_TRUE; | 
|  | } | 
|  |  | 
|  | void* TestGLSurface::GetProcAddress(const char* name) const { | 
|  | if (name == nullptr) { | 
|  | return nullptr; | 
|  | } | 
|  | auto symbol = ::eglGetProcAddress(name); | 
|  | if (symbol == NULL) { | 
|  | FML_LOG(ERROR) << "Could not fetch symbol for name: " << name; | 
|  | } | 
|  | return reinterpret_cast<void*>(symbol); | 
|  | } | 
|  |  | 
|  | sk_sp<GrDirectContext> TestGLSurface::GetGrContext() { | 
|  | if (context_) { | 
|  | return context_; | 
|  | } | 
|  |  | 
|  | return CreateGrContext(); | 
|  | } | 
|  |  | 
|  | sk_sp<GrDirectContext> TestGLSurface::CreateGrContext() { | 
|  | if (!MakeCurrent()) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | auto get_string = | 
|  | reinterpret_cast<PFNGLGETSTRINGPROC>(GetProcAddress("glGetString")); | 
|  |  | 
|  | if (!get_string) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | auto c_version = reinterpret_cast<const char*>(get_string(GL_VERSION)); | 
|  |  | 
|  | if (c_version == NULL) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | GrGLGetProc get_proc = [](void* context, const char name[]) -> GrGLFuncPtr { | 
|  | return reinterpret_cast<GrGLFuncPtr>( | 
|  | reinterpret_cast<TestGLSurface*>(context)->GetProcAddress(name)); | 
|  | }; | 
|  |  | 
|  | std::string version(c_version); | 
|  | auto interface = version.find("OpenGL ES") == std::string::npos | 
|  | ? GrGLMakeAssembledGLInterface(this, get_proc) | 
|  | : GrGLMakeAssembledGLESInterface(this, get_proc); | 
|  |  | 
|  | if (!interface) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | context_ = GrDirectContext::MakeGL(interface); | 
|  | return context_; | 
|  | } | 
|  |  | 
|  | sk_sp<SkSurface> TestGLSurface::GetOnscreenSurface() { | 
|  | FML_CHECK(::eglGetCurrentContext() != EGL_NO_CONTEXT); | 
|  |  | 
|  | GrGLFramebufferInfo framebuffer_info = {}; | 
|  | const uint32_t width = surface_size_.width(); | 
|  | const uint32_t height = surface_size_.height(); | 
|  | framebuffer_info.fFBOID = GetFramebuffer(width, height); | 
|  | #if FML_OS_MACOSX | 
|  | framebuffer_info.fFormat = 0x8058;  // GL_RGBA8 | 
|  | #else | 
|  | framebuffer_info.fFormat = 0x93A1;  // GL_BGRA8; | 
|  | #endif | 
|  |  | 
|  | auto backend_render_target = | 
|  | GrBackendRenderTargets::MakeGL(width,            // width | 
|  | height,           // height | 
|  | 1,                // sample count | 
|  | 8,                // stencil bits | 
|  | framebuffer_info  // framebuffer info | 
|  | ); | 
|  |  | 
|  | SkSurfaceProps surface_properties(0, kUnknown_SkPixelGeometry); | 
|  |  | 
|  | auto surface = SkSurfaces::WrapBackendRenderTarget( | 
|  | GetGrContext().get(),         // context | 
|  | backend_render_target,        // backend render target | 
|  | kBottomLeft_GrSurfaceOrigin,  // surface origin | 
|  | kN32_SkColorType,             // color type | 
|  | SkColorSpace::MakeSRGB(),     // color space | 
|  | &surface_properties,          // surface properties | 
|  | nullptr,                      // release proc | 
|  | nullptr                       // release context | 
|  | ); | 
|  |  | 
|  | if (!surface) { | 
|  | FML_LOG(ERROR) << "Could not wrap the surface while attempting to " | 
|  | "snapshot the GL surface."; | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | return surface; | 
|  | } | 
|  |  | 
|  | sk_sp<SkImage> TestGLSurface::GetRasterSurfaceSnapshot() { | 
|  | auto surface = GetOnscreenSurface(); | 
|  |  | 
|  | if (!surface) { | 
|  | FML_LOG(ERROR) << "Aborting snapshot because of on-screen surface " | 
|  | "acquisition failure."; | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | auto device_snapshot = surface->makeImageSnapshot(); | 
|  |  | 
|  | if (!device_snapshot) { | 
|  | FML_LOG(ERROR) << "Could not create the device snapshot while attempting " | 
|  | "to snapshot the GL surface."; | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | auto host_snapshot = device_snapshot->makeRasterImage(); | 
|  |  | 
|  | if (!host_snapshot) { | 
|  | FML_LOG(ERROR) << "Could not create the host snapshot while attempting to " | 
|  | "snapshot the GL surface."; | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | return host_snapshot; | 
|  | } | 
|  |  | 
|  | uint32_t TestGLSurface::GetWindowFBOId() const { | 
|  | return 0u; | 
|  | } | 
|  |  | 
|  | }  // namespace testing | 
|  | }  // namespace flutter |