1 // Copyright (c) 2012 The Chromium 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 "ui/gl/gl_gl_api_implementation.h"
6 
7 #include <vector>
8 
9 #include "base/stl_util.h"
10 #include "base/strings/string_split.h"
11 #include "base/strings/string_util.h"
12 #include "ui/gl/gl_context.h"
13 #include "ui/gl/gl_implementation.h"
14 #include "ui/gl/gl_state_restorer.h"
15 #include "ui/gl/gl_surface.h"
16 #include "ui/gl/gl_switches.h"
17 #include "ui/gl/gl_version_info.h"
18 #include "ui/gl/shader_tracking.h"
19 
20 namespace gl {
21 
22 // The GL state for when no context is bound
23 static CurrentGL* g_no_context_current_gl = nullptr;
24 
25 // If the null draw bindings are currently enabled.
26 // TODO: Consider adding a new GLApi that no-ops these functions
27 static bool g_null_draw_bindings_enabled = false;
28 
29 namespace {
30 
31 // TODO(epenner): Could the above function be merged into GetInternalFormat and
32 // removed?
GetTexInternalFormat(const GLVersionInfo * version,GLenum internal_format,GLenum format,GLenum type)33 static inline GLenum GetTexInternalFormat(const GLVersionInfo* version,
34                                           GLenum internal_format,
35                                           GLenum format,
36                                           GLenum type) {
37   GLenum gl_internal_format = GetInternalFormat(version, internal_format);
38 
39   // g_version_info must be initialized when this function is bound.
40   if (version->is_es3) {
41     if (internal_format == GL_RED_EXT) {
42       // GL_EXT_texture_rg case in ES2.
43       switch (type) {
44         case GL_UNSIGNED_BYTE:
45           gl_internal_format = GL_R8_EXT;
46           break;
47         case GL_UNSIGNED_SHORT:
48           gl_internal_format = GL_R16_EXT;
49           break;
50         case GL_HALF_FLOAT_OES:
51           gl_internal_format = GL_R16F_EXT;
52           break;
53         case GL_FLOAT:
54           gl_internal_format = GL_R32F_EXT;
55           break;
56         default:
57           NOTREACHED();
58           break;
59       }
60       return gl_internal_format;
61     } else if (internal_format == GL_RG_EXT) {
62       // GL_EXT_texture_rg case in ES2.
63       switch (type) {
64         case GL_UNSIGNED_BYTE:
65           gl_internal_format = GL_RG8_EXT;
66           break;
67         case GL_HALF_FLOAT_OES:
68           gl_internal_format = GL_RG16F_EXT;
69           break;
70         case GL_FLOAT:
71           gl_internal_format = GL_RG32F_EXT;
72           break;
73         default:
74           NOTREACHED();
75           break;
76       }
77       return gl_internal_format;
78     }
79   }
80 
81   if (version->IsAtLeastGL(2, 1) || version->IsAtLeastGLES(3, 0)) {
82     switch (internal_format) {
83       case GL_SRGB_EXT:
84         gl_internal_format = GL_SRGB8;
85         break;
86       case GL_SRGB_ALPHA_EXT:
87         gl_internal_format = GL_SRGB8_ALPHA8;
88         break;
89       default:
90         break;
91     }
92   }
93 
94   if (version->is_es2)
95     return gl_internal_format;
96 
97   // For ES3, use sized float/half_float internal formats whenever posssible.
98   if (type == GL_FLOAT) {
99     switch (internal_format) {
100       // We need to map all the unsized internal formats from ES2 clients.
101       case GL_RGBA:
102         gl_internal_format = GL_RGBA32F;
103         break;
104       case GL_RGB:
105         gl_internal_format = GL_RGB32F;
106         break;
107       case GL_LUMINANCE_ALPHA:
108         if (!version->is_es)
109           gl_internal_format = GL_LUMINANCE_ALPHA32F_ARB;
110         break;
111       case GL_LUMINANCE:
112         if (!version->is_es)
113           gl_internal_format = GL_LUMINANCE32F_ARB;
114         break;
115       case GL_ALPHA:
116         if (!version->is_es)
117           gl_internal_format = GL_ALPHA32F_ARB;
118         break;
119       default:
120         // We can't assert here because if the client context is ES3,
121         // all sized internal_format will reach here.
122         break;
123     }
124   } else if (type == GL_HALF_FLOAT_OES) {
125     switch (internal_format) {
126       case GL_RGBA:
127         gl_internal_format = GL_RGBA16F;
128         break;
129       case GL_RGB:
130         gl_internal_format = GL_RGB16F;
131         break;
132       case GL_LUMINANCE_ALPHA:
133         if (!version->is_es)
134           gl_internal_format = GL_LUMINANCE_ALPHA16F_ARB;
135         break;
136       case GL_LUMINANCE:
137         if (!version->is_es)
138           gl_internal_format = GL_LUMINANCE16F_ARB;
139         break;
140       case GL_ALPHA:
141         if (!version->is_es)
142           gl_internal_format = GL_ALPHA16F_ARB;
143         break;
144       default:
145         break;
146     }
147   }
148 
149   return gl_internal_format;
150 }
151 
GetTexFormat(const GLVersionInfo * version,GLenum format)152 static inline GLenum GetTexFormat(const GLVersionInfo* version, GLenum format) {
153   GLenum gl_format = format;
154 
155   if (version->IsAtLeastGL(2, 1) || version->IsAtLeastGLES(3, 0)) {
156     switch (format) {
157       case GL_SRGB_EXT:
158         gl_format = GL_RGB;
159         break;
160       case GL_SRGB_ALPHA_EXT:
161         gl_format = GL_RGBA;
162         break;
163       default:
164         break;
165     }
166   }
167 
168   return gl_format;
169 }
170 
GetPixelType(const GLVersionInfo * version,GLenum type,GLenum format)171 static inline GLenum GetPixelType(const GLVersionInfo* version,
172                                   GLenum type,
173                                   GLenum format) {
174   if (!version->is_es2) {
175     if (type == GL_HALF_FLOAT_OES) {
176       if (version->is_es) {
177         // For ES3+, use HALF_FLOAT instead of HALF_FLOAT_OES whenever possible.
178         switch (format) {
179           case GL_LUMINANCE:
180           case GL_LUMINANCE_ALPHA:
181           case GL_ALPHA:
182             return type;
183           default:
184             break;
185         }
186       }
187       return GL_HALF_FLOAT;
188     }
189   }
190   return type;
191 }
192 
193 }  // anonymous namespace
194 
GetInternalFormat(const GLVersionInfo * version,GLenum internal_format)195 GLenum GetInternalFormat(const GLVersionInfo* version, GLenum internal_format) {
196   if (!version->is_es) {
197     if (internal_format == GL_BGRA_EXT || internal_format == GL_BGRA8_EXT)
198       return GL_RGBA8;
199   }
200   if (version->is_es3 && version->is_mesa) {
201     // Mesa bug workaround: Mipmapping does not work when using GL_BGRA_EXT
202     if (internal_format == GL_BGRA_EXT)
203       return GL_RGBA;
204   }
205   return internal_format;
206 }
207 
InitializeStaticGLBindingsGL()208 void InitializeStaticGLBindingsGL() {
209   g_current_gl_context_tls = new base::ThreadLocalPointer<CurrentGL>;
210   g_no_context_current_gl = new CurrentGL;
211   g_no_context_current_gl->Api = new NoContextGLApi;
212 }
213 
ClearBindingsGL()214 void ClearBindingsGL() {
215   if (g_no_context_current_gl) {
216     delete g_no_context_current_gl->Api;
217     delete g_no_context_current_gl->Driver;
218     delete g_no_context_current_gl->Version;
219     delete g_no_context_current_gl;
220     g_no_context_current_gl = nullptr;
221   }
222 
223   if (g_current_gl_context_tls) {
224     delete g_current_gl_context_tls;
225     g_current_gl_context_tls = nullptr;
226   }
227 }
228 
SetNullDrawGLBindingsEnabled(bool enabled)229 bool SetNullDrawGLBindingsEnabled(bool enabled) {
230   bool old_value = g_null_draw_bindings_enabled;
231   g_null_draw_bindings_enabled = enabled;
232   return old_value;
233 }
234 
GetNullDrawBindingsEnabled()235 bool GetNullDrawBindingsEnabled() {
236   return g_null_draw_bindings_enabled;
237 }
238 
SetCurrentGL(CurrentGL * current)239 void SetCurrentGL(CurrentGL* current) {
240   CurrentGL* new_current = current ? current : g_no_context_current_gl;
241   g_current_gl_context_tls->Set(new_current);
242 }
243 
GLApi()244 GLApi::GLApi() {
245 }
246 
~GLApi()247 GLApi::~GLApi() {
248 }
249 
GLApiBase()250 GLApiBase::GLApiBase() : driver_(nullptr) {}
251 
~GLApiBase()252 GLApiBase::~GLApiBase() {
253 }
254 
InitializeBase(DriverGL * driver)255 void GLApiBase::InitializeBase(DriverGL* driver) {
256   driver_ = driver;
257 }
258 
RealGLApi()259 RealGLApi::RealGLApi() {
260 }
261 
~RealGLApi()262 RealGLApi::~RealGLApi() {
263 }
264 
Initialize(DriverGL * driver)265 void RealGLApi::Initialize(DriverGL* driver) {
266   InitializeBase(driver);
267 }
268 
glGetIntegervFn(GLenum pname,GLint * params)269 void RealGLApi::glGetIntegervFn(GLenum pname, GLint* params) {
270   if (pname == GL_NUM_EXTENSIONS && disabled_exts_.size()) {
271     InitializeFilteredExtensionsIfNeeded();
272     *params = static_cast<GLint>(filtered_exts_.size());
273   } else {
274     GLApiBase::glGetIntegervFn(pname, params);
275   }
276 }
277 
glGetStringFn(GLenum name)278 const GLubyte* RealGLApi::glGetStringFn(GLenum name) {
279   if (name == GL_EXTENSIONS && disabled_exts_.size()) {
280     InitializeFilteredExtensionsIfNeeded();
281     return reinterpret_cast<const GLubyte*>(filtered_exts_str_.c_str());
282   }
283   return GLApiBase::glGetStringFn(name);
284 }
285 
glGetStringiFn(GLenum name,GLuint index)286 const GLubyte* RealGLApi::glGetStringiFn(GLenum name, GLuint index) {
287   if (name == GL_EXTENSIONS && disabled_exts_.size()) {
288     InitializeFilteredExtensionsIfNeeded();
289     if (index >= filtered_exts_.size()) {
290       return nullptr;
291     }
292     return reinterpret_cast<const GLubyte*>(filtered_exts_[index].c_str());
293   }
294   return GLApiBase::glGetStringiFn(name, index);
295 }
296 
glTexImage2DFn(GLenum target,GLint level,GLint internalformat,GLsizei width,GLsizei height,GLint border,GLenum format,GLenum type,const void * pixels)297 void RealGLApi::glTexImage2DFn(GLenum target,
298                                GLint level,
299                                GLint internalformat,
300                                GLsizei width,
301                                GLsizei height,
302                                GLint border,
303                                GLenum format,
304                                GLenum type,
305                                const void* pixels) {
306   GLenum gl_internal_format =
307       GetTexInternalFormat(version_.get(), internalformat, format, type);
308   GLenum gl_format = GetTexFormat(version_.get(), format);
309   GLenum gl_type = GetPixelType(version_.get(), type, format);
310 
311   // TODO(yizhou): Check if cubemap, 3d texture or texture2d array has the same
312   // bug on intel mac.
313   if (!version_->is_angle && gl_workarounds_.reset_teximage2d_base_level &&
314       target == GL_TEXTURE_2D) {
315     GLint base_level = 0;
316     GLApiBase::glGetTexParameterivFn(target, GL_TEXTURE_BASE_LEVEL,
317                                      &base_level);
318     if (base_level) {
319       GLApiBase::glTexParameteriFn(target, GL_TEXTURE_BASE_LEVEL, 0);
320       GLApiBase::glTexImage2DFn(target, level, gl_internal_format, width,
321                                 height, border, gl_format, gl_type, pixels);
322       GLApiBase::glTexParameteriFn(target, GL_TEXTURE_BASE_LEVEL, base_level);
323       return;
324     }
325   }
326   GLApiBase::glTexImage2DFn(target, level, gl_internal_format, width, height,
327                             border, gl_format, gl_type, pixels);
328 }
329 
glTexSubImage2DFn(GLenum target,GLint level,GLint xoffset,GLint yoffset,GLsizei width,GLsizei height,GLenum format,GLenum type,const void * pixels)330 void RealGLApi::glTexSubImage2DFn(GLenum target,
331                                   GLint level,
332                                   GLint xoffset,
333                                   GLint yoffset,
334                                   GLsizei width,
335                                   GLsizei height,
336                                   GLenum format,
337                                   GLenum type,
338                                   const void* pixels) {
339   GLenum gl_format = GetTexFormat(version_.get(), format);
340   GLenum gl_type = GetPixelType(version_.get(), type, format);
341   GLApiBase::glTexSubImage2DFn(target, level, xoffset, yoffset, width, height,
342                                gl_format, gl_type, pixels);
343 }
344 
glTexStorage2DEXTFn(GLenum target,GLsizei levels,GLenum internalformat,GLsizei width,GLsizei height)345 void RealGLApi::glTexStorage2DEXTFn(GLenum target,
346                                     GLsizei levels,
347                                     GLenum internalformat,
348                                     GLsizei width,
349                                     GLsizei height) {
350   GLenum gl_internal_format = GetInternalFormat(version_.get(), internalformat);
351   GLApiBase::glTexStorage2DEXTFn(target, levels, gl_internal_format, width,
352                                  height);
353 }
354 
glTexStorageMem2DEXTFn(GLenum target,GLsizei levels,GLenum internalformat,GLsizei width,GLsizei height,GLuint memory,GLuint64 offset)355 void RealGLApi::glTexStorageMem2DEXTFn(GLenum target,
356                                        GLsizei levels,
357                                        GLenum internalformat,
358                                        GLsizei width,
359                                        GLsizei height,
360                                        GLuint memory,
361                                        GLuint64 offset) {
362   internalformat = GetInternalFormat(version_.get(), internalformat);
363   GLApiBase::glTexStorageMem2DEXTFn(target, levels, internalformat, width,
364                                     height, memory, offset);
365 }
366 
glTexStorageMemFlags2DANGLEFn(GLenum target,GLsizei levels,GLenum internalformat,GLsizei width,GLsizei height,GLuint memory,GLuint64 offset,GLbitfield createFlags,GLbitfield usageFlags)367 void RealGLApi::glTexStorageMemFlags2DANGLEFn(GLenum target,
368                                               GLsizei levels,
369                                               GLenum internalformat,
370                                               GLsizei width,
371                                               GLsizei height,
372                                               GLuint memory,
373                                               GLuint64 offset,
374                                               GLbitfield createFlags,
375                                               GLbitfield usageFlags) {
376   internalformat = GetInternalFormat(version_.get(), internalformat);
377   GLApiBase::glTexStorageMemFlags2DANGLEFn(target, levels, internalformat,
378                                            width, height, memory, offset,
379                                            createFlags, usageFlags);
380 }
381 
glRenderbufferStorageEXTFn(GLenum target,GLenum internalformat,GLsizei width,GLsizei height)382 void RealGLApi::glRenderbufferStorageEXTFn(GLenum target,
383                                            GLenum internalformat,
384                                            GLsizei width,
385                                            GLsizei height) {
386   GLenum gl_internal_format = GetInternalFormat(version_.get(), internalformat);
387   GLApiBase::glRenderbufferStorageEXTFn(target, gl_internal_format, width,
388                                         height);
389 }
390 
391 // The ANGLE and IMG variants of glRenderbufferStorageMultisample currently do
392 // not support BGRA render buffers so only the EXT one is customized. If
393 // GL_CHROMIUM_renderbuffer_format_BGRA8888 support is added to ANGLE then the
394 // ANGLE version should also be customized.
glRenderbufferStorageMultisampleEXTFn(GLenum target,GLsizei samples,GLenum internalformat,GLsizei width,GLsizei height)395 void RealGLApi::glRenderbufferStorageMultisampleEXTFn(GLenum target,
396                                                       GLsizei samples,
397                                                       GLenum internalformat,
398                                                       GLsizei width,
399                                                       GLsizei height) {
400   GLenum gl_internal_format = GetInternalFormat(version_.get(), internalformat);
401   GLApiBase::glRenderbufferStorageMultisampleEXTFn(
402       target, samples, gl_internal_format, width, height);
403 }
404 
glRenderbufferStorageMultisampleFn(GLenum target,GLsizei samples,GLenum internalformat,GLsizei width,GLsizei height)405 void RealGLApi::glRenderbufferStorageMultisampleFn(GLenum target,
406                                                    GLsizei samples,
407                                                    GLenum internalformat,
408                                                    GLsizei width,
409                                                    GLsizei height) {
410   GLenum gl_internal_format = GetInternalFormat(version_.get(), internalformat);
411   GLApiBase::glRenderbufferStorageMultisampleFn(
412       target, samples, gl_internal_format, width, height);
413 }
414 
glReadPixelsFn(GLint x,GLint y,GLsizei width,GLsizei height,GLenum format,GLenum type,void * pixels)415 void RealGLApi::glReadPixelsFn(GLint x,
416                                GLint y,
417                                GLsizei width,
418                                GLsizei height,
419                                GLenum format,
420                                GLenum type,
421                                void* pixels) {
422   GLenum gl_type = GetPixelType(version_.get(), type, format);
423   GLApiBase::glReadPixelsFn(x, y, width, height, format, gl_type, pixels);
424 }
425 
glClearFn(GLbitfield mask)426 void RealGLApi::glClearFn(GLbitfield mask) {
427   if (!g_null_draw_bindings_enabled)
428     GLApiBase::glClearFn(mask);
429 }
430 
glClearColorFn(GLclampf red,GLclampf green,GLclampf blue,GLclampf alpha)431 void RealGLApi::glClearColorFn(GLclampf red,
432                                GLclampf green,
433                                GLclampf blue,
434                                GLclampf alpha) {
435   if (!version_->is_angle && gl_workarounds_.clear_to_zero_or_one_broken &&
436       (1 == red || 0 == red) && (1 == green || 0 == green) &&
437       (1 == blue || 0 == blue) && (1 == alpha || 0 == alpha)) {
438     if (1 == alpha)
439       alpha = 2;
440     else
441       alpha = -1;
442   }
443   GLApiBase::glClearColorFn(red, green, blue, alpha);
444 }
445 
glDrawArraysFn(GLenum mode,GLint first,GLsizei count)446 void RealGLApi::glDrawArraysFn(GLenum mode, GLint first, GLsizei count) {
447   if (!g_null_draw_bindings_enabled)
448     GLApiBase::glDrawArraysFn(mode, first, count);
449 }
450 
glDrawElementsFn(GLenum mode,GLsizei count,GLenum type,const void * indices)451 void RealGLApi::glDrawElementsFn(GLenum mode,
452                                  GLsizei count,
453                                  GLenum type,
454                                  const void* indices) {
455   if (!g_null_draw_bindings_enabled)
456     GLApiBase::glDrawElementsFn(mode, count, type, indices);
457 }
458 
glClearDepthFn(GLclampd depth)459 void RealGLApi::glClearDepthFn(GLclampd depth) {
460   // OpenGL ES only has glClearDepthf, forward the parameters from glClearDepth.
461   // Many mock tests expect only glClearDepth is called so don't make the
462   // interception when testing with mocks.
463   if (version_->is_es && GetGLImplementation() != kGLImplementationMockGL) {
464     DCHECK(driver_->fn.glClearDepthfFn);
465     GLApiBase::glClearDepthfFn(static_cast<GLclampf>(depth));
466   } else {
467     DCHECK(driver_->fn.glClearDepthFn);
468     GLApiBase::glClearDepthFn(depth);
469   }
470 }
471 
glDepthRangeFn(GLclampd z_near,GLclampd z_far)472 void RealGLApi::glDepthRangeFn(GLclampd z_near, GLclampd z_far) {
473   // OpenGL ES only has glDepthRangef, forward the parameters from glDepthRange.
474   // Many mock tests expect only glDepthRange is called so don't make the
475   // interception when testing with mocks.
476   if (version_->is_es && GetGLImplementation() != kGLImplementationMockGL) {
477     DCHECK(driver_->fn.glDepthRangefFn);
478     GLApiBase::glDepthRangefFn(static_cast<GLclampf>(z_near),
479                                static_cast<GLclampf>(z_far));
480   } else {
481     DCHECK(driver_->fn.glDepthRangeFn);
482     GLApiBase::glDepthRangeFn(z_near, z_far);
483   }
484 }
485 
glUseProgramFn(GLuint program)486 void RealGLApi::glUseProgramFn(GLuint program) {
487   ShaderTracking* shader_tracking = ShaderTracking::GetInstance();
488   if (shader_tracking) {
489     std::vector<char> buffers[2];
490     char* strings[2] = {nullptr, nullptr};
491     if (program) {
492       // The following only works with ANGLE backend because ANGLE makes sure
493       // a program's shaders are not actually deleted and source can still be
494       // queried even if glDeleteShaders() has been called on them.
495 
496       // Also, in theory, different shaders can be attached to the program
497       // after the last link, but for now, ignore such corner case patterns.
498       GLsizei count = 0;
499       GLuint shaders[2] = {0};
500       glGetAttachedShadersFn(program, 2, &count, shaders);
501       for (GLsizei ii = 0; ii < std::min(2, count); ++ii) {
502         buffers[ii].resize(ShaderTracking::kMaxShaderSize);
503         glGetShaderSourceFn(shaders[ii], ShaderTracking::kMaxShaderSize,
504                             nullptr, buffers[ii].data());
505         strings[ii] = buffers[ii].data();
506       }
507     }
508     shader_tracking->SetShaders(strings[0], strings[1]);
509   }
510   GLApiBase::glUseProgramFn(program);
511 }
512 
InitializeFilteredExtensionsIfNeeded()513 void RealGLApi::InitializeFilteredExtensionsIfNeeded() {
514   DCHECK(disabled_exts_.size());
515   if (filtered_exts_.size())
516     return;
517   DCHECK(filtered_exts_str_.empty());
518   if (WillUseGLGetStringForExtensions(this)) {
519     filtered_exts_str_ = FilterGLExtensionList(
520         reinterpret_cast<const char*>(GLApiBase::glGetStringFn(GL_EXTENSIONS)),
521         disabled_exts_);
522     filtered_exts_ = base::SplitString(
523         filtered_exts_str_, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
524   } else {
525     GLint num_extensions = 0;
526     GLApiBase::glGetIntegervFn(GL_NUM_EXTENSIONS, &num_extensions);
527     for (GLint i = 0; i < num_extensions; ++i) {
528       const char* gl_extension = reinterpret_cast<const char*>(
529           GLApiBase::glGetStringiFn(GL_EXTENSIONS, i));
530       DCHECK(gl_extension);
531       if (!base::Contains(disabled_exts_, gl_extension))
532         filtered_exts_.push_back(gl_extension);
533     }
534     filtered_exts_str_ = base::JoinString(filtered_exts_, " ");
535   }
536 }
537 
SetDisabledExtensions(const std::string & disabled_extensions)538 void RealGLApi::SetDisabledExtensions(const std::string& disabled_extensions) {
539   ClearCachedGLExtensions();
540   disabled_exts_.clear();
541   if (disabled_extensions.empty())
542     return;
543   disabled_exts_ =
544       base::SplitString(disabled_extensions, ", ;", base::KEEP_WHITESPACE,
545                         base::SPLIT_WANT_NONEMPTY);
546   DCHECK(disabled_exts_.size());
547 }
548 
ClearCachedGLExtensions()549 void RealGLApi::ClearCachedGLExtensions() {
550   filtered_exts_.clear();
551   filtered_exts_str_.clear();
552 }
553 
set_gl_workarounds(const GLWorkarounds & workarounds)554 void RealGLApi::set_gl_workarounds(const GLWorkarounds& workarounds) {
555   gl_workarounds_ = workarounds;
556 }
557 
set_version(std::unique_ptr<GLVersionInfo> version)558 void RealGLApi::set_version(std::unique_ptr<GLVersionInfo> version) {
559   version_ = std::move(version);
560 }
561 
~TraceGLApi()562 TraceGLApi::~TraceGLApi() {
563 }
564 
LogGLApi(GLApi * gl_api)565 LogGLApi::LogGLApi(GLApi* gl_api) : gl_api_(gl_api) {}
566 
~LogGLApi()567 LogGLApi::~LogGLApi() {}
568 
NoContextGLApi()569 NoContextGLApi::NoContextGLApi() {
570 }
571 
~NoContextGLApi()572 NoContextGLApi::~NoContextGLApi() {
573 }
574 
575 }  // namespace gl
576