1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "flutter/testing/test_gl_surface.h"
6
7#include <EGL/egl.h>
8#include <EGL/eglext.h>
9#include <EGL/eglplatform.h>
10#include <GLES2/gl2.h>
11
12#include <sstream>
13#include <string>
14
15#include "flutter/fml/build_config.h"
16#include "flutter/fml/logging.h"
17#include "third_party/skia/include/core/SkColorSpace.h"
18#include "third_party/skia/include/core/SkColorType.h"
19#include "third_party/skia/include/core/SkSurface.h"
20#include "third_party/skia/include/gpu/GrBackendSurface.h"
21#include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h"
22#include "third_party/skia/include/gpu/ganesh/gl/GrGLBackendSurface.h"
23#include "third_party/skia/include/gpu/gl/GrGLAssembleInterface.h"
24#include "third_party/skia/include/gpu/gl/GrGLTypes.h"
25
26namespace flutter {
27namespace testing {
28
29static std::string GetEGLError() {
30 std::stringstream stream;
31
32 auto error = ::eglGetError();
33
34 stream << "EGL Result: '";
35
36 switch (error) {
37 case EGL_SUCCESS:
38 stream << "EGL_SUCCESS";
39 break;
40 case EGL_NOT_INITIALIZED:
41 stream << "EGL_NOT_INITIALIZED";
42 break;
43 case EGL_BAD_ACCESS:
44 stream << "EGL_BAD_ACCESS";
45 break;
46 case EGL_BAD_ALLOC:
47 stream << "EGL_BAD_ALLOC";
48 break;
49 case EGL_BAD_ATTRIBUTE:
50 stream << "EGL_BAD_ATTRIBUTE";
51 break;
52 case EGL_BAD_CONTEXT:
53 stream << "EGL_BAD_CONTEXT";
54 break;
55 case EGL_BAD_CONFIG:
56 stream << "EGL_BAD_CONFIG";
57 break;
58 case EGL_BAD_CURRENT_SURFACE:
59 stream << "EGL_BAD_CURRENT_SURFACE";
60 break;
61 case EGL_BAD_DISPLAY:
62 stream << "EGL_BAD_DISPLAY";
63 break;
64 case EGL_BAD_SURFACE:
65 stream << "EGL_BAD_SURFACE";
66 break;
67 case EGL_BAD_MATCH:
68 stream << "EGL_BAD_MATCH";
69 break;
70 case EGL_BAD_PARAMETER:
71 stream << "EGL_BAD_PARAMETER";
72 break;
73 case EGL_BAD_NATIVE_PIXMAP:
74 stream << "EGL_BAD_NATIVE_PIXMAP";
75 break;
76 case EGL_BAD_NATIVE_WINDOW:
77 stream << "EGL_BAD_NATIVE_WINDOW";
78 break;
79 case EGL_CONTEXT_LOST:
80 stream << "EGL_CONTEXT_LOST";
81 break;
82 default:
83 stream << "Unknown";
84 }
85
86 stream << "' (0x" << std::hex << error << std::dec << ").";
87 return stream.str();
88}
89
90static bool HasExtension(const char* extensions, const char* name) {
91 const char* r = strstr(s1: extensions, s2: name);
92 auto len = strlen(s: name);
93 // check that the extension name is terminated by space or null terminator
94 return r != nullptr && (r[len] == ' ' || r[len] == 0);
95}
96
97static void CheckSwanglekExtensions() {
98 const char* extensions = ::eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
99 FML_CHECK(HasExtension(extensions, "EGL_EXT_platform_base")) << extensions;
100 FML_CHECK(HasExtension(extensions, "EGL_ANGLE_platform_angle_vulkan"))
101 << extensions;
102 FML_CHECK(HasExtension(extensions,
103 "EGL_ANGLE_platform_angle_device_type_swiftshader"))
104 << extensions;
105}
106
107static EGLDisplay CreateSwangleDisplay() {
108 CheckSwanglekExtensions();
109
110 PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_EXT =
111 reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
112 eglGetProcAddress(procname: "eglGetPlatformDisplayEXT"));
113 FML_CHECK(egl_get_platform_display_EXT)
114 << "eglGetPlatformDisplayEXT not available.";
115
116 const EGLint display_config[] = {
117 EGL_PLATFORM_ANGLE_TYPE_ANGLE,
118 EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE,
119 EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE,
120 EGL_PLATFORM_ANGLE_DEVICE_TYPE_SWIFTSHADER_ANGLE,
121 EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE,
122 EGL_PLATFORM_VULKAN_DISPLAY_MODE_HEADLESS_ANGLE,
123 EGL_NONE,
124 };
125
126 return egl_get_platform_display_EXT(
127 EGL_PLATFORM_ANGLE_ANGLE,
128 reinterpret_cast<EGLNativeDisplayType*>(EGL_DEFAULT_DISPLAY),
129 display_config);
130}
131
132TestGLSurface::TestGLSurface(SkISize surface_size)
133 : surface_size_(surface_size) {
134 display_ = CreateSwangleDisplay();
135 FML_CHECK(display_ != EGL_NO_DISPLAY);
136
137 auto result = ::eglInitialize(dpy: display_, major: nullptr, minor: nullptr);
138 FML_CHECK(result == EGL_TRUE) << GetEGLError();
139
140 EGLConfig config = {0};
141
142 EGLint num_config = 0;
143 const EGLint attribute_list[] = {EGL_RED_SIZE,
144 8,
145 EGL_GREEN_SIZE,
146 8,
147 EGL_BLUE_SIZE,
148 8,
149 EGL_ALPHA_SIZE,
150 8,
151 EGL_SURFACE_TYPE,
152 EGL_PBUFFER_BIT,
153 EGL_CONFORMANT,
154 EGL_OPENGL_ES2_BIT,
155 EGL_RENDERABLE_TYPE,
156 EGL_OPENGL_ES2_BIT,
157 EGL_NONE};
158
159 result = ::eglChooseConfig(dpy: display_, attrib_list: attribute_list, configs: &config, config_size: 1, num_config: &num_config);
160 FML_CHECK(result == EGL_TRUE) << GetEGLError();
161 FML_CHECK(num_config == 1) << GetEGLError();
162
163 {
164 const EGLint onscreen_surface_attributes[] = {
165 EGL_WIDTH, surface_size_.width(), //
166 EGL_HEIGHT, surface_size_.height(), //
167 EGL_NONE,
168 };
169
170 onscreen_surface_ = ::eglCreatePbufferSurface(
171 dpy: display_, // display connection
172 config, // config
173 attrib_list: onscreen_surface_attributes // surface attributes
174 );
175 FML_CHECK(onscreen_surface_ != EGL_NO_SURFACE) << GetEGLError();
176 }
177
178 {
179 const EGLint offscreen_surface_attributes[] = {
180 EGL_WIDTH, 1, //
181 EGL_HEIGHT, 1, //
182 EGL_NONE,
183 };
184 offscreen_surface_ = ::eglCreatePbufferSurface(
185 dpy: display_, // display connection
186 config, // config
187 attrib_list: offscreen_surface_attributes // surface attributes
188 );
189 FML_CHECK(offscreen_surface_ != EGL_NO_SURFACE) << GetEGLError();
190 }
191
192 {
193 const EGLint context_attributes[] = {
194 EGL_CONTEXT_CLIENT_VERSION, //
195 2, //
196 EGL_NONE //
197 };
198
199 onscreen_context_ =
200 ::eglCreateContext(dpy: display_, // display connection
201 config, // config
202 EGL_NO_CONTEXT, // sharegroup
203 attrib_list: context_attributes // context attributes
204 );
205 FML_CHECK(onscreen_context_ != EGL_NO_CONTEXT) << GetEGLError();
206
207 offscreen_context_ =
208 ::eglCreateContext(dpy: display_, // display connection
209 config, // config
210 share_context: onscreen_context_, // sharegroup
211 attrib_list: context_attributes // context attributes
212 );
213 FML_CHECK(offscreen_context_ != EGL_NO_CONTEXT) << GetEGLError();
214 }
215}
216
217TestGLSurface::~TestGLSurface() {
218 context_ = nullptr;
219
220 auto result = ::eglDestroyContext(dpy: display_, ctx: onscreen_context_);
221 FML_CHECK(result == EGL_TRUE) << GetEGLError();
222
223 result = ::eglDestroyContext(dpy: display_, ctx: offscreen_context_);
224 FML_CHECK(result == EGL_TRUE) << GetEGLError();
225
226 result = ::eglDestroySurface(dpy: display_, surface: onscreen_surface_);
227 FML_CHECK(result == EGL_TRUE) << GetEGLError();
228
229 result = ::eglDestroySurface(dpy: display_, surface: offscreen_surface_);
230 FML_CHECK(result == EGL_TRUE) << GetEGLError();
231
232 result = ::eglTerminate(dpy: display_);
233 FML_CHECK(result == EGL_TRUE);
234}
235
236const SkISize& TestGLSurface::GetSurfaceSize() const {
237 return surface_size_;
238}
239
240bool TestGLSurface::MakeCurrent() {
241 auto result = ::eglMakeCurrent(dpy: display_, draw: onscreen_surface_, read: onscreen_surface_,
242 ctx: onscreen_context_);
243
244 if (result == EGL_FALSE) {
245 FML_LOG(ERROR) << "Could not make the context current. " << GetEGLError();
246 }
247
248 return result == EGL_TRUE;
249}
250
251bool TestGLSurface::ClearCurrent() {
252 auto result = ::eglMakeCurrent(dpy: display_, EGL_NO_SURFACE, EGL_NO_SURFACE,
253 EGL_NO_CONTEXT);
254
255 if (result == EGL_FALSE) {
256 FML_LOG(ERROR) << "Could not clear the current context. " << GetEGLError();
257 }
258
259 return result == EGL_TRUE;
260}
261
262bool TestGLSurface::Present() {
263 auto result = ::eglSwapBuffers(dpy: display_, surface: onscreen_surface_);
264
265 if (result == EGL_FALSE) {
266 FML_LOG(ERROR) << "Could not swap buffers. " << GetEGLError();
267 }
268
269 return result == EGL_TRUE;
270}
271
272uint32_t TestGLSurface::GetFramebuffer(uint32_t width, uint32_t height) const {
273 return GetWindowFBOId();
274}
275
276bool TestGLSurface::MakeResourceCurrent() {
277 auto result = ::eglMakeCurrent(dpy: display_, draw: offscreen_surface_,
278 read: offscreen_surface_, ctx: offscreen_context_);
279
280 if (result == EGL_FALSE) {
281 FML_LOG(ERROR) << "Could not make the resource context current. "
282 << GetEGLError();
283 }
284
285 return result == EGL_TRUE;
286}
287
288void* TestGLSurface::GetProcAddress(const char* name) const {
289 if (name == nullptr) {
290 return nullptr;
291 }
292 auto symbol = ::eglGetProcAddress(procname: name);
293 if (symbol == NULL) {
294 FML_LOG(ERROR) << "Could not fetch symbol for name: " << name;
295 }
296 return reinterpret_cast<void*>(symbol);
297}
298
299sk_sp<GrDirectContext> TestGLSurface::GetGrContext() {
300 if (context_) {
301 return context_;
302 }
303
304 return CreateGrContext();
305}
306
307sk_sp<GrDirectContext> TestGLSurface::CreateGrContext() {
308 if (!MakeCurrent()) {
309 return nullptr;
310 }
311
312 auto get_string =
313 reinterpret_cast<PFNGLGETSTRINGPROC>(GetProcAddress(name: "glGetString"));
314
315 if (!get_string) {
316 return nullptr;
317 }
318
319 auto c_version = reinterpret_cast<const char*>(get_string(GL_VERSION));
320
321 if (c_version == NULL) {
322 return nullptr;
323 }
324
325 GrGLGetProc get_proc = [](void* context, const char name[]) -> GrGLFuncPtr {
326 return reinterpret_cast<GrGLFuncPtr>(
327 reinterpret_cast<TestGLSurface*>(context)->GetProcAddress(name));
328 };
329
330 std::string version(c_version);
331 auto interface = version.find(s: "OpenGL ES") == std::string::npos
332 ? GrGLMakeAssembledGLInterface(ctx: this, get: get_proc)
333 : GrGLMakeAssembledGLESInterface(ctx: this, get: get_proc);
334
335 if (!interface) {
336 return nullptr;
337 }
338
339 context_ = GrDirectContext::MakeGL(interface);
340 return context_;
341}
342
343sk_sp<SkSurface> TestGLSurface::GetOnscreenSurface() {
344 FML_CHECK(::eglGetCurrentContext() != EGL_NO_CONTEXT);
345
346 GrGLFramebufferInfo framebuffer_info = {};
347 const uint32_t width = surface_size_.width();
348 const uint32_t height = surface_size_.height();
349 framebuffer_info.fFBOID = GetFramebuffer(width, height);
350#if FML_OS_MACOSX
351 framebuffer_info.fFormat = 0x8058; // GL_RGBA8
352#else
353 framebuffer_info.fFormat = 0x93A1; // GL_BGRA8;
354#endif
355
356 auto backend_render_target =
357 GrBackendRenderTargets::MakeGL(width, // width
358 height, // height
359 sampleCnt: 1, // sample count
360 stencilBits: 8, // stencil bits
361 glInfo: framebuffer_info // framebuffer info
362 );
363
364 SkSurfaceProps surface_properties(0, kUnknown_SkPixelGeometry);
365
366 auto surface = SkSurfaces::WrapBackendRenderTarget(
367 context: GetGrContext().get(), // context
368 backendRenderTarget: backend_render_target, // backend render target
369 origin: kBottomLeft_GrSurfaceOrigin, // surface origin
370 colorType: kN32_SkColorType, // color type
371 colorSpace: SkColorSpace::MakeSRGB(), // color space
372 surfaceProps: &surface_properties, // surface properties
373 releaseProc: nullptr, // release proc
374 releaseContext: nullptr // release context
375 );
376
377 if (!surface) {
378 FML_LOG(ERROR) << "Could not wrap the surface while attempting to "
379 "snapshot the GL surface.";
380 return nullptr;
381 }
382
383 return surface;
384}
385
386sk_sp<SkImage> TestGLSurface::GetRasterSurfaceSnapshot() {
387 auto surface = GetOnscreenSurface();
388
389 if (!surface) {
390 FML_LOG(ERROR) << "Aborting snapshot because of on-screen surface "
391 "acquisition failure.";
392 return nullptr;
393 }
394
395 auto device_snapshot = surface->makeImageSnapshot();
396
397 if (!device_snapshot) {
398 FML_LOG(ERROR) << "Could not create the device snapshot while attempting "
399 "to snapshot the GL surface.";
400 return nullptr;
401 }
402
403 auto host_snapshot = device_snapshot->makeRasterImage();
404
405 if (!host_snapshot) {
406 FML_LOG(ERROR) << "Could not create the host snapshot while attempting to "
407 "snapshot the GL surface.";
408 return nullptr;
409 }
410
411 return host_snapshot;
412}
413
414uint32_t TestGLSurface::GetWindowFBOId() const {
415 return 0u;
416}
417
418} // namespace testing
419} // namespace flutter
420

source code of flutter_engine/flutter/testing/test_gl_surface.cc