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 <GLES2/gl2.h>
9
10#include <sstream>
11#include <string>
12
13#include "flutter/fml/build_config.h"
14#include "flutter/fml/logging.h"
15#include "third_party/skia/include/core/SkSurface.h"
16#include "third_party/skia/include/gpu/gl/GrGLAssembleInterface.h"
17#include "third_party/skia/src/gpu/gl/GrGLDefines.h"
18
19namespace flutter {
20namespace testing {
21
22static std::string GetEGLError() {
23 std::stringstream stream;
24
25 auto error = ::eglGetError();
26
27 stream << "EGL Result: '";
28
29 switch (error) {
30 case EGL_SUCCESS:
31 stream << "EGL_SUCCESS";
32 break;
33 case EGL_NOT_INITIALIZED:
34 stream << "EGL_NOT_INITIALIZED";
35 break;
36 case EGL_BAD_ACCESS:
37 stream << "EGL_BAD_ACCESS";
38 break;
39 case EGL_BAD_ALLOC:
40 stream << "EGL_BAD_ALLOC";
41 break;
42 case EGL_BAD_ATTRIBUTE:
43 stream << "EGL_BAD_ATTRIBUTE";
44 break;
45 case EGL_BAD_CONTEXT:
46 stream << "EGL_BAD_CONTEXT";
47 break;
48 case EGL_BAD_CONFIG:
49 stream << "EGL_BAD_CONFIG";
50 break;
51 case EGL_BAD_CURRENT_SURFACE:
52 stream << "EGL_BAD_CURRENT_SURFACE";
53 break;
54 case EGL_BAD_DISPLAY:
55 stream << "EGL_BAD_DISPLAY";
56 break;
57 case EGL_BAD_SURFACE:
58 stream << "EGL_BAD_SURFACE";
59 break;
60 case EGL_BAD_MATCH:
61 stream << "EGL_BAD_MATCH";
62 break;
63 case EGL_BAD_PARAMETER:
64 stream << "EGL_BAD_PARAMETER";
65 break;
66 case EGL_BAD_NATIVE_PIXMAP:
67 stream << "EGL_BAD_NATIVE_PIXMAP";
68 break;
69 case EGL_BAD_NATIVE_WINDOW:
70 stream << "EGL_BAD_NATIVE_WINDOW";
71 break;
72 case EGL_CONTEXT_LOST:
73 stream << "EGL_CONTEXT_LOST";
74 break;
75 default:
76 stream << "Unknown";
77 }
78
79 stream << "' (0x" << std::hex << error << std::dec << ").";
80 return stream.str();
81}
82
83TestGLSurface::TestGLSurface(SkISize surface_size)
84 : surface_size_(surface_size) {
85 display_ = ::eglGetDisplay(EGL_DEFAULT_DISPLAY);
86 FML_CHECK(display_ != EGL_NO_DISPLAY);
87
88 auto result = ::eglInitialize(display_, NULL, NULL);
89 FML_CHECK(result == EGL_TRUE) << GetEGLError();
90
91 EGLConfig config = {0};
92
93 EGLint num_config = 0;
94 const EGLint attribute_list[] = {EGL_RED_SIZE,
95 8,
96 EGL_GREEN_SIZE,
97 8,
98 EGL_BLUE_SIZE,
99 8,
100 EGL_ALPHA_SIZE,
101 8,
102 EGL_SURFACE_TYPE,
103 EGL_PBUFFER_BIT,
104 EGL_CONFORMANT,
105 EGL_OPENGL_ES2_BIT,
106 EGL_RENDERABLE_TYPE,
107 EGL_OPENGL_ES2_BIT,
108 EGL_NONE};
109
110 result = ::eglChooseConfig(display_, attribute_list, &config, 1, &num_config);
111 FML_CHECK(result == EGL_TRUE) << GetEGLError();
112 FML_CHECK(num_config == 1) << GetEGLError();
113
114 {
115 const EGLint onscreen_surface_attributes[] = {
116 EGL_WIDTH, surface_size_.width(), //
117 EGL_HEIGHT, surface_size_.height(), //
118 EGL_NONE,
119 };
120
121 onscreen_surface_ = ::eglCreatePbufferSurface(
122 display_, // display connection
123 config, // config
124 onscreen_surface_attributes // surface attributes
125 );
126 FML_CHECK(onscreen_surface_ != EGL_NO_SURFACE) << GetEGLError();
127 }
128
129 {
130 const EGLint offscreen_surface_attributes[] = {
131 EGL_WIDTH, 1, //
132 EGL_HEIGHT, 1, //
133 EGL_NONE,
134 };
135 offscreen_surface_ = ::eglCreatePbufferSurface(
136 display_, // display connection
137 config, // config
138 offscreen_surface_attributes // surface attributes
139 );
140 FML_CHECK(offscreen_surface_ != EGL_NO_SURFACE) << GetEGLError();
141 }
142
143 {
144 const EGLint context_attributes[] = {
145 EGL_CONTEXT_CLIENT_VERSION, //
146 2, //
147 EGL_NONE //
148 };
149
150 onscreen_context_ =
151 ::eglCreateContext(display_, // display connection
152 config, // config
153 EGL_NO_CONTEXT, // sharegroup
154 context_attributes // context attributes
155 );
156 FML_CHECK(onscreen_context_ != EGL_NO_CONTEXT) << GetEGLError();
157
158 offscreen_context_ =
159 ::eglCreateContext(display_, // display connection
160 config, // config
161 onscreen_context_, // sharegroup
162 context_attributes // context attributes
163 );
164 FML_CHECK(offscreen_context_ != EGL_NO_CONTEXT) << GetEGLError();
165 }
166}
167
168TestGLSurface::~TestGLSurface() {
169 context_ = nullptr;
170
171 auto result = ::eglDestroyContext(display_, onscreen_context_);
172 FML_CHECK(result == EGL_TRUE) << GetEGLError();
173
174 result = ::eglDestroyContext(display_, offscreen_context_);
175 FML_CHECK(result == EGL_TRUE) << GetEGLError();
176
177 result = ::eglDestroySurface(display_, onscreen_surface_);
178 FML_CHECK(result == EGL_TRUE) << GetEGLError();
179
180 result = ::eglDestroySurface(display_, offscreen_surface_);
181 FML_CHECK(result == EGL_TRUE) << GetEGLError();
182
183 result = ::eglTerminate(display_);
184 FML_CHECK(result == EGL_TRUE);
185}
186
187const SkISize& TestGLSurface::GetSurfaceSize() const {
188 return surface_size_;
189}
190
191bool TestGLSurface::MakeCurrent() {
192 auto result = ::eglMakeCurrent(display_, onscreen_surface_, onscreen_surface_,
193 onscreen_context_);
194
195 if (result == EGL_FALSE) {
196 FML_LOG(ERROR) << "Could not make the context current. " << GetEGLError();
197 }
198
199 return result == EGL_TRUE;
200}
201
202bool TestGLSurface::ClearCurrent() {
203 auto result = ::eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE,
204 EGL_NO_CONTEXT);
205
206 if (result == EGL_FALSE) {
207 FML_LOG(ERROR) << "Could not clear the current context. " << GetEGLError();
208 }
209
210 return result == EGL_TRUE;
211}
212
213bool TestGLSurface::Present() {
214 auto result = ::eglSwapBuffers(display_, onscreen_surface_);
215
216 if (result == EGL_FALSE) {
217 FML_LOG(ERROR) << "Could not swap buffers. " << GetEGLError();
218 }
219
220 return result == EGL_TRUE;
221}
222
223uint32_t TestGLSurface::GetFramebuffer() const {
224 // Return FBO0
225 return 0;
226}
227
228bool TestGLSurface::MakeResourceCurrent() {
229 auto result = ::eglMakeCurrent(display_, offscreen_surface_,
230 offscreen_surface_, offscreen_context_);
231
232 if (result == EGL_FALSE) {
233 FML_LOG(ERROR) << "Could not make the resource context current. "
234 << GetEGLError();
235 }
236
237 return result == EGL_TRUE;
238}
239
240void* TestGLSurface::GetProcAddress(const char* name) const {
241 if (name == nullptr) {
242 return nullptr;
243 }
244 auto symbol = ::eglGetProcAddress(name);
245 if (symbol == NULL) {
246 FML_LOG(ERROR) << "Could not fetch symbol for name: " << name;
247 }
248 return reinterpret_cast<void*>(symbol);
249}
250
251sk_sp<GrDirectContext> TestGLSurface::GetGrContext() {
252 if (context_) {
253 return context_;
254 }
255
256 return CreateGrContext();
257}
258
259sk_sp<GrDirectContext> TestGLSurface::CreateGrContext() {
260 if (!MakeCurrent()) {
261 return nullptr;
262 }
263
264 auto get_string =
265 reinterpret_cast<PFNGLGETSTRINGPROC>(GetProcAddress("glGetString"));
266
267 if (!get_string) {
268 return nullptr;
269 }
270
271 auto c_version = reinterpret_cast<const char*>(get_string(GL_VERSION));
272
273 if (c_version == NULL) {
274 return nullptr;
275 }
276
277 GrGLGetProc get_proc = [](void* context, const char name[]) -> GrGLFuncPtr {
278 return reinterpret_cast<GrGLFuncPtr>(
279 reinterpret_cast<TestGLSurface*>(context)->GetProcAddress(name));
280 };
281
282 std::string version(c_version);
283 auto interface = version.find("OpenGL ES") == std::string::npos
284 ? GrGLMakeAssembledGLInterface(this, get_proc)
285 : GrGLMakeAssembledGLESInterface(this, get_proc);
286
287 if (!interface) {
288 return nullptr;
289 }
290
291 context_ = GrDirectContext::MakeGL(interface);
292 return context_;
293}
294
295sk_sp<SkSurface> TestGLSurface::GetOnscreenSurface() {
296 FML_CHECK(::eglGetCurrentContext() != EGL_NO_CONTEXT);
297
298 GrGLFramebufferInfo framebuffer_info = {};
299 framebuffer_info.fFBOID = GetFramebuffer();
300#if OS_MACOSX
301 framebuffer_info.fFormat = GR_GL_RGBA8;
302#else
303 framebuffer_info.fFormat = GR_GL_BGRA8;
304#endif
305
306 GrBackendRenderTarget backend_render_target(
307 surface_size_.width(), // width
308 surface_size_.height(), // height
309 1, // sample count
310 8, // stencil bits
311 framebuffer_info // framebuffer info
312 );
313
314 SkSurfaceProps surface_properties(
315 SkSurfaceProps::InitType::kLegacyFontHost_InitType);
316
317 auto surface = SkSurface::MakeFromBackendRenderTarget(
318 GetGrContext().get(), // context
319 backend_render_target, // backend render target
320 kBottomLeft_GrSurfaceOrigin, // surface origin
321 kN32_SkColorType, // color type
322 SkColorSpace::MakeSRGB(), // color space
323 &surface_properties, // surface properties
324 nullptr, // release proc
325 nullptr // release context
326 );
327
328 if (!surface) {
329 FML_LOG(ERROR) << "Could not wrap the surface while attempting to "
330 "snapshot the GL surface.";
331 return nullptr;
332 }
333
334 return surface;
335}
336
337sk_sp<SkImage> TestGLSurface::GetRasterSurfaceSnapshot() {
338 auto surface = GetOnscreenSurface();
339
340 if (!surface) {
341 FML_LOG(ERROR) << "Aborting snapshot because of on-screen surface "
342 "acquisition failure.";
343 return nullptr;
344 }
345
346 auto device_snapshot = surface->makeImageSnapshot();
347
348 if (!device_snapshot) {
349 FML_LOG(ERROR) << "Could not create the device snapshot while attempting "
350 "to snapshot the GL surface.";
351 return nullptr;
352 }
353
354 auto host_snapshot = device_snapshot->makeRasterImage();
355
356 if (!host_snapshot) {
357 FML_LOG(ERROR) << "Could not create the host snapshot while attempting to "
358 "snapshot the GL surface.";
359 return nullptr;
360 }
361
362 return host_snapshot;
363}
364
365} // namespace testing
366} // namespace flutter
367