| // Copyright (c) 2012 The Chromium 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 "ui/gl/gl_gl_api_implementation.h" |
| |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "ui/gl/gl_context.h" |
| #include "ui/gl/gl_implementation.h" |
| #include "ui/gl/gl_state_restorer.h" |
| #include "ui/gl/gl_surface.h" |
| #include "ui/gl/gl_switches.h" |
| #include "ui/gl/gl_version_info.h" |
| |
| namespace gl { |
| |
| // The GL Api being used. This could be g_real_gl or gl_trace_gl |
| static GLApi* g_gl = NULL; |
| // A GL Api that calls directly into the driver. |
| static RealGLApi* g_real_gl = NULL; |
| // A GL Api that does nothing but warn about illegal GL calls without a context |
| // current. |
| static NoContextGLApi* g_no_context_gl = NULL; |
| // A GL Api that calls TRACE and then calls another GL api. |
| static TraceGLApi* g_trace_gl = NULL; |
| // GL version used when initializing dynamic bindings. |
| static GLVersionInfo* g_version_info = NULL; |
| |
| namespace { |
| |
| static inline GLenum GetInternalFormat(GLenum internal_format) { |
| if (GetGLImplementation() != kGLImplementationEGLGLES2) { |
| if (internal_format == GL_BGRA_EXT || internal_format == GL_BGRA8_EXT) |
| return GL_RGBA8; |
| } |
| return internal_format; |
| } |
| |
| // TODO(epenner): Could the above function be merged into this and removed? |
| static inline GLenum GetTexInternalFormat(GLenum internal_format, |
| GLenum format, |
| GLenum type) { |
| GLenum gl_internal_format = GetInternalFormat(internal_format); |
| |
| // g_version_info must be initialized when this function is bound. |
| DCHECK(g_version_info); |
| if (g_version_info->is_es3) { |
| if (internal_format == GL_RED_EXT) { |
| // GL_EXT_texture_rg case in ES2. |
| switch (type) { |
| case GL_UNSIGNED_BYTE: |
| gl_internal_format = GL_R8_EXT; |
| break; |
| case GL_HALF_FLOAT_OES: |
| gl_internal_format = GL_R16F_EXT; |
| break; |
| case GL_FLOAT: |
| gl_internal_format = GL_R32F_EXT; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| return gl_internal_format; |
| } else if (internal_format == GL_RG_EXT) { |
| // GL_EXT_texture_rg case in ES2. |
| switch (type) { |
| case GL_UNSIGNED_BYTE: |
| gl_internal_format = GL_RG8_EXT; |
| break; |
| case GL_HALF_FLOAT_OES: |
| gl_internal_format = GL_RG16F_EXT; |
| break; |
| case GL_FLOAT: |
| gl_internal_format = GL_RG32F_EXT; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| return gl_internal_format; |
| } |
| } |
| |
| if (type == GL_FLOAT && g_version_info->is_angle && g_version_info->is_es && |
| g_version_info->major_version == 2) { |
| // It's possible that the texture is using a sized internal format, and |
| // ANGLE exposing GLES2 API doesn't support those. |
| // TODO(oetuaho@nvidia.com): Remove these conversions once ANGLE has the |
| // support. |
| // http://code.google.com/p/angleproject/issues/detail?id=556 |
| switch (format) { |
| case GL_RGBA: |
| gl_internal_format = GL_RGBA; |
| break; |
| case GL_RGB: |
| gl_internal_format = GL_RGB; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| if (g_version_info->IsAtLeastGL(2, 1) || |
| g_version_info->IsAtLeastGLES(3, 0)) { |
| switch (internal_format) { |
| case GL_SRGB_EXT: |
| gl_internal_format = GL_SRGB8; |
| break; |
| case GL_SRGB_ALPHA_EXT: |
| gl_internal_format = GL_SRGB8_ALPHA8; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| if (g_version_info->is_es) |
| return gl_internal_format; |
| |
| if (type == GL_FLOAT) { |
| switch (internal_format) { |
| // We need to map all the unsized internal formats from ES2 clients. |
| case GL_RGBA: |
| gl_internal_format = GL_RGBA32F_ARB; |
| break; |
| case GL_RGB: |
| gl_internal_format = GL_RGB32F_ARB; |
| break; |
| case GL_LUMINANCE_ALPHA: |
| gl_internal_format = GL_LUMINANCE_ALPHA32F_ARB; |
| break; |
| case GL_LUMINANCE: |
| gl_internal_format = GL_LUMINANCE32F_ARB; |
| break; |
| case GL_ALPHA: |
| gl_internal_format = GL_ALPHA32F_ARB; |
| break; |
| default: |
| // We can't assert here because if the client context is ES3, |
| // all sized internal_format will reach here. |
| break; |
| } |
| } else if (type == GL_HALF_FLOAT_OES) { |
| switch (internal_format) { |
| case GL_RGBA: |
| gl_internal_format = GL_RGBA16F_ARB; |
| break; |
| case GL_RGB: |
| gl_internal_format = GL_RGB16F_ARB; |
| break; |
| case GL_LUMINANCE_ALPHA: |
| gl_internal_format = GL_LUMINANCE_ALPHA16F_ARB; |
| break; |
| case GL_LUMINANCE: |
| gl_internal_format = GL_LUMINANCE16F_ARB; |
| break; |
| case GL_ALPHA: |
| gl_internal_format = GL_ALPHA16F_ARB; |
| break; |
| // RED and RG are reached here because on Desktop GL core profile, |
| // LUMINANCE/ALPHA formats are emulated through RED and RG in Chrome. |
| case GL_RED: |
| gl_internal_format = GL_R16F; |
| break; |
| case GL_RG: |
| gl_internal_format = GL_RG16F; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| return gl_internal_format; |
| } |
| |
| static inline GLenum GetTexFormat(GLenum format) { |
| GLenum gl_format = format; |
| |
| DCHECK(g_version_info); |
| if (g_version_info->IsAtLeastGL(2, 1) || |
| g_version_info->IsAtLeastGLES(3, 0)) { |
| switch (format) { |
| case GL_SRGB_EXT: |
| gl_format = GL_RGB; |
| break; |
| case GL_SRGB_ALPHA_EXT: |
| gl_format = GL_RGBA; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| return gl_format; |
| } |
| |
| static inline GLenum GetTexType(GLenum type) { |
| if (GetGLImplementation() != kGLImplementationEGLGLES2) { |
| if (type == GL_HALF_FLOAT_OES) |
| return GL_HALF_FLOAT_ARB; |
| } |
| return type; |
| } |
| |
| static void GL_BINDING_CALL CustomTexImage2D( |
| GLenum target, GLint level, GLint internalformat, |
| GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, |
| const void* pixels) { |
| GLenum gl_internal_format = GetTexInternalFormat( |
| internalformat, format, type); |
| GLenum gl_format = GetTexFormat(format); |
| GLenum gl_type = GetTexType(type); |
| g_driver_gl.orig_fn.glTexImage2DFn( |
| target, level, gl_internal_format, width, height, border, gl_format, |
| gl_type, pixels); |
| } |
| |
| static void GL_BINDING_CALL CustomTexSubImage2D( |
| GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, |
| GLsizei height, GLenum format, GLenum type, const void* pixels) { |
| GLenum gl_format = GetTexFormat(format); |
| GLenum gl_type = GetTexType(type); |
| g_driver_gl.orig_fn.glTexSubImage2DFn( |
| target, level, xoffset, yoffset, width, height, gl_format, gl_type, |
| pixels); |
| } |
| |
| static void GL_BINDING_CALL CustomTexStorage2DEXT( |
| GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, |
| GLsizei height) { |
| GLenum gl_internal_format = GetInternalFormat(internalformat); |
| g_driver_gl.orig_fn.glTexStorage2DEXTFn( |
| target, levels, gl_internal_format, width, height); |
| } |
| |
| static void GL_BINDING_CALL CustomRenderbufferStorageEXT( |
| GLenum target, GLenum internalformat, GLsizei width, GLsizei height) { |
| GLenum gl_internal_format = GetInternalFormat(internalformat); |
| g_driver_gl.orig_fn.glRenderbufferStorageEXTFn( |
| target, gl_internal_format, width, height); |
| } |
| |
| // The ANGLE and IMG variants of glRenderbufferStorageMultisample currently do |
| // not support BGRA render buffers so only the EXT one is customized. If |
| // GL_CHROMIUM_renderbuffer_format_BGRA8888 support is added to ANGLE then the |
| // ANGLE version should also be customized. |
| static void GL_BINDING_CALL CustomRenderbufferStorageMultisampleEXT( |
| GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, |
| GLsizei height) { |
| GLenum gl_internal_format = GetInternalFormat(internalformat); |
| g_driver_gl.orig_fn.glRenderbufferStorageMultisampleEXTFn( |
| target, samples, gl_internal_format, width, height); |
| } |
| |
| } // anonymous namespace |
| |
| void DriverGL::InitializeCustomDynamicBindings(GLContext* context) { |
| InitializeDynamicBindings(context); |
| |
| DCHECK(orig_fn.glTexImage2DFn == NULL); |
| orig_fn.glTexImage2DFn = fn.glTexImage2DFn; |
| fn.glTexImage2DFn = |
| reinterpret_cast<glTexImage2DProc>(CustomTexImage2D); |
| |
| DCHECK(orig_fn.glTexSubImage2DFn == NULL); |
| orig_fn.glTexSubImage2DFn = fn.glTexSubImage2DFn; |
| fn.glTexSubImage2DFn = |
| reinterpret_cast<glTexSubImage2DProc>(CustomTexSubImage2D); |
| |
| DCHECK(orig_fn.glTexStorage2DEXTFn == NULL); |
| orig_fn.glTexStorage2DEXTFn = fn.glTexStorage2DEXTFn; |
| fn.glTexStorage2DEXTFn = |
| reinterpret_cast<glTexStorage2DEXTProc>(CustomTexStorage2DEXT); |
| |
| DCHECK(orig_fn.glRenderbufferStorageEXTFn == NULL); |
| orig_fn.glRenderbufferStorageEXTFn = fn.glRenderbufferStorageEXTFn; |
| fn.glRenderbufferStorageEXTFn = |
| reinterpret_cast<glRenderbufferStorageEXTProc>( |
| CustomRenderbufferStorageEXT); |
| |
| DCHECK(orig_fn.glRenderbufferStorageMultisampleEXTFn == NULL); |
| orig_fn.glRenderbufferStorageMultisampleEXTFn = |
| fn.glRenderbufferStorageMultisampleEXTFn; |
| fn.glRenderbufferStorageMultisampleEXTFn = |
| reinterpret_cast<glRenderbufferStorageMultisampleEXTProc>( |
| CustomRenderbufferStorageMultisampleEXT); |
| } |
| |
| static void GL_BINDING_CALL NullDrawClearFn(GLbitfield mask) { |
| if (!g_driver_gl.null_draw_bindings_enabled) |
| g_driver_gl.orig_fn.glClearFn(mask); |
| } |
| |
| static void GL_BINDING_CALL |
| NullDrawDrawArraysFn(GLenum mode, GLint first, GLsizei count) { |
| if (!g_driver_gl.null_draw_bindings_enabled) |
| g_driver_gl.orig_fn.glDrawArraysFn(mode, first, count); |
| } |
| |
| static void GL_BINDING_CALL NullDrawDrawElementsFn(GLenum mode, |
| GLsizei count, |
| GLenum type, |
| const void* indices) { |
| if (!g_driver_gl.null_draw_bindings_enabled) |
| g_driver_gl.orig_fn.glDrawElementsFn(mode, count, type, indices); |
| } |
| |
| void DriverGL::InitializeNullDrawBindings() { |
| DCHECK(orig_fn.glClearFn == NULL); |
| orig_fn.glClearFn = fn.glClearFn; |
| fn.glClearFn = NullDrawClearFn; |
| |
| DCHECK(orig_fn.glDrawArraysFn == NULL); |
| orig_fn.glDrawArraysFn = fn.glDrawArraysFn; |
| fn.glDrawArraysFn = NullDrawDrawArraysFn; |
| |
| DCHECK(orig_fn.glDrawElementsFn == NULL); |
| orig_fn.glDrawElementsFn = fn.glDrawElementsFn; |
| fn.glDrawElementsFn = NullDrawDrawElementsFn; |
| |
| null_draw_bindings_enabled = true; |
| } |
| |
| bool DriverGL::HasInitializedNullDrawBindings() { |
| return orig_fn.glClearFn != NULL && orig_fn.glDrawArraysFn != NULL && |
| orig_fn.glDrawElementsFn != NULL; |
| } |
| |
| bool DriverGL::SetNullDrawBindingsEnabled(bool enabled) { |
| DCHECK(orig_fn.glClearFn != NULL); |
| DCHECK(orig_fn.glDrawArraysFn != NULL); |
| DCHECK(orig_fn.glDrawElementsFn != NULL); |
| |
| bool before = null_draw_bindings_enabled; |
| null_draw_bindings_enabled = enabled; |
| return before; |
| } |
| |
| void InitializeStaticGLBindingsGL() { |
| g_current_gl_context_tls = new base::ThreadLocalPointer<GLApi>; |
| g_driver_gl.InitializeStaticBindings(); |
| if (!g_real_gl) { |
| g_real_gl = new RealGLApi(); |
| g_trace_gl = new TraceGLApi(g_real_gl); |
| g_no_context_gl = new NoContextGLApi(); |
| } |
| g_real_gl->Initialize(&g_driver_gl); |
| g_gl = g_real_gl; |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableGPUServiceTracing)) { |
| g_gl = g_trace_gl; |
| } |
| SetGLToRealGLApi(); |
| } |
| |
| GLApi* GetCurrentGLApi() { |
| return g_current_gl_context_tls ? g_current_gl_context_tls->Get() : nullptr; |
| } |
| |
| void SetGLApi(GLApi* api) { |
| g_current_gl_context_tls->Set(api); |
| } |
| |
| void SetGLToRealGLApi() { |
| SetGLApi(g_gl); |
| } |
| |
| void SetGLApiToNoContext() { |
| SetGLApi(g_no_context_gl); |
| } |
| |
| const GLVersionInfo* GetGLVersionInfo() { |
| return g_version_info; |
| } |
| |
| void InitializeDynamicGLBindingsGL(GLContext* context) { |
| if (g_version_info) |
| return; |
| g_real_gl->InitializeFilteredExtensions(); |
| g_driver_gl.InitializeCustomDynamicBindings(context); |
| DCHECK(context && context->IsCurrent(NULL) && !g_version_info); |
| g_version_info = new GLVersionInfo( |
| context->GetGLVersion().c_str(), |
| context->GetGLRenderer().c_str(), |
| context->GetExtensions().c_str()); |
| } |
| |
| void InitializeDebugGLBindingsGL() { |
| g_driver_gl.InitializeDebugBindings(); |
| } |
| |
| void InitializeNullDrawGLBindingsGL() { |
| g_driver_gl.InitializeNullDrawBindings(); |
| } |
| |
| bool HasInitializedNullDrawGLBindingsGL() { |
| return g_driver_gl.HasInitializedNullDrawBindings(); |
| } |
| |
| bool SetNullDrawGLBindingsEnabledGL(bool enabled) { |
| return g_driver_gl.SetNullDrawBindingsEnabled(enabled); |
| } |
| |
| void ClearGLBindingsGL() { |
| if (g_real_gl) { |
| delete g_real_gl; |
| g_real_gl = NULL; |
| } |
| if (g_trace_gl) { |
| delete g_trace_gl; |
| g_trace_gl = NULL; |
| } |
| if (g_no_context_gl) { |
| delete g_no_context_gl; |
| g_no_context_gl = NULL; |
| } |
| g_gl = NULL; |
| g_driver_gl.ClearBindings(); |
| if (g_current_gl_context_tls) { |
| delete g_current_gl_context_tls; |
| g_current_gl_context_tls = NULL; |
| } |
| if (g_version_info) { |
| delete g_version_info; |
| g_version_info = NULL; |
| } |
| } |
| |
| GLApi::GLApi() { |
| } |
| |
| GLApi::~GLApi() { |
| if (GetCurrentGLApi() == this) |
| SetGLApi(NULL); |
| } |
| |
| GLApiBase::GLApiBase() |
| : driver_(NULL) { |
| } |
| |
| GLApiBase::~GLApiBase() { |
| } |
| |
| void GLApiBase::InitializeBase(DriverGL* driver) { |
| driver_ = driver; |
| } |
| |
| RealGLApi::RealGLApi() { |
| #if DCHECK_IS_ON() |
| filtered_exts_initialized_ = false; |
| #endif |
| } |
| |
| RealGLApi::~RealGLApi() { |
| } |
| |
| void RealGLApi::Initialize(DriverGL* driver) { |
| InitializeWithCommandLine(driver, base::CommandLine::ForCurrentProcess()); |
| } |
| |
| void RealGLApi::InitializeWithCommandLine(DriverGL* driver, |
| base::CommandLine* command_line) { |
| DCHECK(command_line); |
| InitializeBase(driver); |
| |
| const std::string disabled_extensions = command_line->GetSwitchValueASCII( |
| switches::kDisableGLExtensions); |
| if (!disabled_extensions.empty()) { |
| disabled_exts_ = base::SplitString( |
| disabled_extensions, ", ;", |
| base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| } |
| } |
| |
| void RealGLApi::glGetIntegervFn(GLenum pname, GLint* params) { |
| if (pname == GL_NUM_EXTENSIONS && disabled_exts_.size()) { |
| #if DCHECK_IS_ON() |
| DCHECK(filtered_exts_initialized_); |
| #endif |
| *params = static_cast<GLint>(filtered_exts_.size()); |
| } else { |
| GLApiBase::glGetIntegervFn(pname, params); |
| } |
| } |
| |
| const GLubyte* RealGLApi::glGetStringFn(GLenum name) { |
| if (name == GL_EXTENSIONS && disabled_exts_.size()) { |
| #if DCHECK_IS_ON() |
| DCHECK(filtered_exts_initialized_); |
| #endif |
| return reinterpret_cast<const GLubyte*>(filtered_exts_str_.c_str()); |
| } |
| return GLApiBase::glGetStringFn(name); |
| } |
| |
| const GLubyte* RealGLApi::glGetStringiFn(GLenum name, GLuint index) { |
| if (name == GL_EXTENSIONS && disabled_exts_.size()) { |
| #if DCHECK_IS_ON() |
| DCHECK(filtered_exts_initialized_); |
| #endif |
| if (index >= filtered_exts_.size()) { |
| return NULL; |
| } |
| return reinterpret_cast<const GLubyte*>(filtered_exts_[index].c_str()); |
| } |
| return GLApiBase::glGetStringiFn(name, index); |
| } |
| |
| void RealGLApi::glFlushFn() { |
| GLApiBase::glFlushFn(); |
| } |
| |
| void RealGLApi::glFinishFn() { |
| GLApiBase::glFinishFn(); |
| } |
| |
| void RealGLApi::InitializeFilteredExtensions() { |
| if (disabled_exts_.size()) { |
| filtered_exts_.clear(); |
| if (WillUseGLGetStringForExtensions()) { |
| filtered_exts_str_ = |
| FilterGLExtensionList(reinterpret_cast<const char*>( |
| GLApiBase::glGetStringFn(GL_EXTENSIONS)), |
| disabled_exts_); |
| filtered_exts_ = base::SplitString( |
| filtered_exts_str_, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| } else { |
| GLint num_extensions = 0; |
| GLApiBase::glGetIntegervFn(GL_NUM_EXTENSIONS, &num_extensions); |
| for (GLint i = 0; i < num_extensions; ++i) { |
| const char* gl_extension = reinterpret_cast<const char*>( |
| GLApiBase::glGetStringiFn(GL_EXTENSIONS, i)); |
| DCHECK(gl_extension != NULL); |
| if (!ContainsValue(disabled_exts_, gl_extension)) |
| filtered_exts_.push_back(gl_extension); |
| } |
| filtered_exts_str_ = base::JoinString(filtered_exts_, " "); |
| } |
| #if DCHECK_IS_ON() |
| filtered_exts_initialized_ = true; |
| #endif |
| } |
| } |
| |
| TraceGLApi::~TraceGLApi() { |
| } |
| |
| NoContextGLApi::NoContextGLApi() { |
| } |
| |
| NoContextGLApi::~NoContextGLApi() { |
| } |
| |
| VirtualGLApi::VirtualGLApi() |
| : real_context_(NULL), |
| current_context_(NULL) { |
| } |
| |
| VirtualGLApi::~VirtualGLApi() { |
| } |
| |
| void VirtualGLApi::Initialize(DriverGL* driver, GLContext* real_context) { |
| InitializeBase(driver); |
| real_context_ = real_context; |
| |
| DCHECK(real_context->IsCurrent(NULL)); |
| extensions_ = real_context->GetExtensions(); |
| extensions_vec_ = base::SplitString(extensions_, " ", base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_ALL); |
| } |
| |
| bool VirtualGLApi::MakeCurrent(GLContext* virtual_context, GLSurface* surface) { |
| bool switched_contexts = g_current_gl_context_tls->Get() != this; |
| GLSurface* current_surface = GLSurface::GetCurrent(); |
| if (switched_contexts || surface != current_surface) { |
| // MakeCurrent 'lite' path that avoids potentially expensive MakeCurrent() |
| // calls if the GLSurface uses the same underlying surface or renders to |
| // an FBO. |
| if (switched_contexts || !current_surface || |
| !virtual_context->IsCurrent(surface)) { |
| if (!real_context_->MakeCurrent(surface)) { |
| return false; |
| } |
| } |
| } |
| |
| DCHECK_EQ(real_context_, GLContext::GetRealCurrent()); |
| DCHECK(real_context_->IsCurrent(NULL)); |
| DCHECK(virtual_context->IsCurrent(surface)); |
| |
| if (switched_contexts || virtual_context != current_context_) { |
| #if DCHECK_IS_ON() |
| GLenum error = glGetErrorFn(); |
| // Accepting a context loss error here enables using debug mode to work on |
| // context loss handling in virtual context mode. |
| // There should be no other errors from the previous context leaking into |
| // the new context. |
| DCHECK(error == GL_NO_ERROR || error == GL_CONTEXT_LOST_KHR) << |
| "GL error was: " << error; |
| #endif |
| |
| // Set all state that is different from the real state |
| GLApi* temp = GetCurrentGLApi(); |
| SetGLToRealGLApi(); |
| if (virtual_context->GetGLStateRestorer()->IsInitialized()) { |
| GLStateRestorer* virtual_state = virtual_context->GetGLStateRestorer(); |
| GLStateRestorer* current_state = current_context_ ? |
| current_context_->GetGLStateRestorer() : |
| nullptr; |
| if (switched_contexts || virtual_context != current_context_) { |
| if (current_state) |
| current_state->PauseQueries(); |
| virtual_state->ResumeQueries(); |
| } |
| |
| virtual_state->RestoreState( |
| (current_state && !switched_contexts) ? current_state : NULL); |
| } |
| SetGLApi(temp); |
| current_context_ = virtual_context; |
| } |
| SetGLApi(this); |
| |
| virtual_context->SetCurrent(surface); |
| if (!surface->OnMakeCurrent(virtual_context)) { |
| LOG(ERROR) << "Could not make GLSurface current."; |
| return false; |
| } |
| return true; |
| } |
| |
| void VirtualGLApi::OnReleaseVirtuallyCurrent(GLContext* virtual_context) { |
| if (current_context_ == virtual_context) |
| current_context_ = NULL; |
| } |
| |
| void VirtualGLApi::glGetIntegervFn(GLenum pname, GLint* params) { |
| switch (pname) { |
| case GL_NUM_EXTENSIONS: |
| *params = static_cast<GLint>(extensions_vec_.size()); |
| break; |
| default: |
| driver_->fn.glGetIntegervFn(pname, params); |
| break; |
| } |
| } |
| |
| const GLubyte* VirtualGLApi::glGetStringFn(GLenum name) { |
| switch (name) { |
| case GL_EXTENSIONS: |
| return reinterpret_cast<const GLubyte*>(extensions_.c_str()); |
| default: |
| return driver_->fn.glGetStringFn(name); |
| } |
| } |
| |
| const GLubyte* VirtualGLApi::glGetStringiFn(GLenum name, GLuint index) { |
| switch (name) { |
| case GL_EXTENSIONS: |
| if (index >= extensions_vec_.size()) |
| return NULL; |
| return reinterpret_cast<const GLubyte*>(extensions_vec_[index].c_str()); |
| default: |
| return driver_->fn.glGetStringiFn(name, index); |
| } |
| } |
| |
| void VirtualGLApi::glFlushFn() { |
| GLApiBase::glFlushFn(); |
| } |
| |
| void VirtualGLApi::glFinishFn() { |
| GLApiBase::glFinishFn(); |
| } |
| |
| } // namespace gl |