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 | |
19 | namespace flutter { |
20 | namespace testing { |
21 | |
22 | static 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 | |
83 | TestGLSurface::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 | |
168 | TestGLSurface::~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 | |
187 | const SkISize& TestGLSurface::GetSurfaceSize() const { |
188 | return surface_size_; |
189 | } |
190 | |
191 | bool 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 | |
202 | bool 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 | |
213 | bool 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 | |
223 | uint32_t TestGLSurface::GetFramebuffer() const { |
224 | // Return FBO0 |
225 | return 0; |
226 | } |
227 | |
228 | bool 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 | |
240 | void* 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 | |
251 | sk_sp<GrDirectContext> TestGLSurface::GetGrContext() { |
252 | if (context_) { |
253 | return context_; |
254 | } |
255 | |
256 | return CreateGrContext(); |
257 | } |
258 | |
259 | sk_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 | |
295 | sk_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 | |
337 | sk_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 | |