// 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 #include #include #include #include #include #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/ganesh/GrBackendSurface.h" #include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" #include "third_party/skia/include/gpu/ganesh/gl/GrGLAssembleInterface.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/ganesh/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( 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(EGL_DEFAULT_DISPLAY), display_config); } TestEGLContext::TestEGLContext() { display = CreateSwangleDisplay(); FML_CHECK(display != EGL_NO_DISPLAY); auto result = ::eglInitialize(display, nullptr, nullptr); FML_CHECK(result == EGL_TRUE) << GetEGLError(); 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 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(); } } TestEGLContext::~TestEGLContext() { auto result = ::eglDestroyContext(display, onscreen_context); FML_CHECK(result == EGL_TRUE) << GetEGLError(); result = ::eglDestroyContext(display, offscreen_context); FML_CHECK(result == EGL_TRUE) << GetEGLError(); result = ::eglTerminate(display); FML_CHECK(result == EGL_TRUE); } TestGLOnscreenOnlySurface::TestGLOnscreenOnlySurface( std::shared_ptr context, SkISize size) : surface_size_(size), egl_context_(std::move(context)) { const EGLint attributes[] = { EGL_WIDTH, size.width(), // EGL_HEIGHT, size.height(), // EGL_NONE, }; onscreen_surface_ = ::eglCreatePbufferSurface(egl_context_->display, // display connection egl_context_->config, // config attributes // surface attributes ); FML_CHECK(onscreen_surface_ != EGL_NO_SURFACE) << GetEGLError(); } TestGLOnscreenOnlySurface::~TestGLOnscreenOnlySurface() { skia_context_ = nullptr; auto result = ::eglDestroySurface(egl_context_->display, onscreen_surface_); FML_CHECK(result == EGL_TRUE) << GetEGLError(); } const SkISize& TestGLOnscreenOnlySurface::GetSurfaceSize() const { return surface_size_; } bool TestGLOnscreenOnlySurface::MakeCurrent() { auto result = ::eglMakeCurrent(egl_context_->display, onscreen_surface_, onscreen_surface_, egl_context_->onscreen_context); if (result == EGL_FALSE) { FML_LOG(ERROR) << "Could not make the context current. " << GetEGLError(); } return result == EGL_TRUE; } bool TestGLOnscreenOnlySurface::ClearCurrent() { auto result = ::eglMakeCurrent(egl_context_->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 TestGLOnscreenOnlySurface::Present() { auto result = ::eglSwapBuffers(egl_context_->display, onscreen_surface_); if (result == EGL_FALSE) { FML_LOG(ERROR) << "Could not swap buffers. " << GetEGLError(); } return result == EGL_TRUE; } uint32_t TestGLOnscreenOnlySurface::GetFramebuffer(uint32_t width, uint32_t height) const { return GetWindowFBOId(); } void* TestGLOnscreenOnlySurface::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(symbol); } sk_sp TestGLOnscreenOnlySurface::GetGrContext() { if (skia_context_) { return skia_context_; } return CreateGrContext(); } sk_sp TestGLOnscreenOnlySurface::CreateGrContext() { if (!MakeCurrent()) { return nullptr; } auto get_string = reinterpret_cast(GetProcAddress("glGetString")); if (!get_string) { return nullptr; } auto c_version = reinterpret_cast(get_string(GL_VERSION)); if (c_version == NULL) { return nullptr; } GrGLGetProc get_proc = [](void* context, const char name[]) -> GrGLFuncPtr { return reinterpret_cast( reinterpret_cast(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; } skia_context_ = GrDirectContexts::MakeGL(interface); return skia_context_; } sk_sp TestGLOnscreenOnlySurface::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 TestGLOnscreenOnlySurface::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 TestGLOnscreenOnlySurface::GetWindowFBOId() const { return 0u; } TestGLSurface::TestGLSurface(SkISize surface_size) : TestGLSurface(std::make_shared(), surface_size) {} TestGLSurface::TestGLSurface(std::shared_ptr egl_context, SkISize surface_size) : TestGLOnscreenOnlySurface(std::move(egl_context), surface_size) { { const EGLint offscreen_surface_attributes[] = { EGL_WIDTH, 1, // EGL_HEIGHT, 1, // EGL_NONE, }; offscreen_surface_ = ::eglCreatePbufferSurface( egl_context_->display, // display connection egl_context_->config, // config offscreen_surface_attributes // surface attributes ); FML_CHECK(offscreen_surface_ != EGL_NO_SURFACE) << GetEGLError(); } } TestGLSurface::~TestGLSurface() { auto result = ::eglDestroySurface(egl_context_->display, offscreen_surface_); FML_CHECK(result == EGL_TRUE) << GetEGLError(); } bool TestGLSurface::MakeResourceCurrent() { auto result = ::eglMakeCurrent(egl_context_->display, offscreen_surface_, offscreen_surface_, egl_context_->offscreen_context); if (result == EGL_FALSE) { FML_LOG(ERROR) << "Could not make the resource context current. " << GetEGLError(); } return result == EGL_TRUE; } } // namespace testing } // namespace flutter