blob: 8c98bacde9fdcc97f8346d553cffa74f524b66ae [file] [log] [blame] [edit]
// 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