| // 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/ganesh/gl/GrGLDirectContext.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_ = GrDirectContexts::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 |