blob: 0dd7cd612835b2b93a81c520a97430e41df385e4 [file] [log] [blame]
// 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/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"
#include "ui/gl/shader_tracking.h"
namespace gl {
// The GL state for when no context is bound
static CurrentGL* g_no_context_current_gl = nullptr;
// If the null draw bindings are currently enabled.
// TODO: Consider adding a new GLApi that no-ops these functions
static bool g_null_draw_bindings_enabled = false;
// If the GL debug bindings are enabled.
static bool g_debug_bindings_enabled = false;
namespace {
// TODO(epenner): Could the above function be merged into GetInternalFormat and
// removed?
static inline GLenum GetTexInternalFormat(const GLVersionInfo* version,
GLenum internal_format,
GLenum format,
GLenum type) {
GLenum gl_internal_format = GetInternalFormat(version, internal_format);
// g_version_info must be initialized when this function is bound.
if (version->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_UNSIGNED_SHORT:
gl_internal_format = GL_R16_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 (version->IsAtLeastGL(2, 1) || version->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 (version->is_es2)
return gl_internal_format;
// For ES3, use sized float/half_float internal formats whenever posssible.
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;
break;
case GL_RGB:
gl_internal_format = GL_RGB32F;
break;
case GL_LUMINANCE_ALPHA:
if (!version->is_es)
gl_internal_format = GL_LUMINANCE_ALPHA32F_ARB;
break;
case GL_LUMINANCE:
if (!version->is_es)
gl_internal_format = GL_LUMINANCE32F_ARB;
break;
case GL_ALPHA:
if (!version->is_es)
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;
break;
case GL_RGB:
gl_internal_format = GL_RGB16F;
break;
case GL_LUMINANCE_ALPHA:
if (!version->is_es)
gl_internal_format = GL_LUMINANCE_ALPHA16F_ARB;
break;
case GL_LUMINANCE:
if (!version->is_es)
gl_internal_format = GL_LUMINANCE16F_ARB;
break;
case GL_ALPHA:
if (!version->is_es)
gl_internal_format = GL_ALPHA16F_ARB;
break;
default:
break;
}
}
return gl_internal_format;
}
static inline GLenum GetTexFormat(const GLVersionInfo* version, GLenum format) {
GLenum gl_format = format;
if (version->IsAtLeastGL(2, 1) || version->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 GetPixelType(const GLVersionInfo* version,
GLenum type,
GLenum format) {
if (!version->is_es2) {
if (type == GL_HALF_FLOAT_OES) {
if (version->is_es) {
// For ES3+, use HALF_FLOAT instead of HALF_FLOAT_OES whenever possible.
switch (format) {
case GL_LUMINANCE:
case GL_LUMINANCE_ALPHA:
case GL_ALPHA:
return type;
default:
break;
}
}
return GL_HALF_FLOAT;
}
}
return type;
}
} // anonymous namespace
GLenum GetInternalFormat(const GLVersionInfo* version, GLenum internal_format) {
if (!version->is_es) {
if (internal_format == GL_BGRA_EXT || internal_format == GL_BGRA8_EXT)
return GL_RGBA8;
}
if (version->is_es3 && version->is_mesa) {
// Mesa bug workaround: Mipmapping does not work when using GL_BGRA_EXT
if (internal_format == GL_BGRA_EXT)
return GL_RGBA;
}
return internal_format;
}
void InitializeStaticGLBindingsGL() {
g_current_gl_context_tls = new base::ThreadLocalPointer<CurrentGL>;
g_no_context_current_gl = new CurrentGL;
g_no_context_current_gl->Api = new NoContextGLApi;
}
void ClearBindingsGL() {
if (g_no_context_current_gl) {
delete g_no_context_current_gl->Api;
delete g_no_context_current_gl->Driver;
delete g_no_context_current_gl->Version;
delete g_no_context_current_gl;
g_no_context_current_gl = nullptr;
}
if (g_current_gl_context_tls) {
delete g_current_gl_context_tls;
g_current_gl_context_tls = nullptr;
}
}
void InitializeDebugGLBindingsGL() {
g_debug_bindings_enabled = true;
}
bool GetDebugGLBindingsInitializedGL() {
return g_debug_bindings_enabled;
}
bool SetNullDrawGLBindingsEnabled(bool enabled) {
bool old_value = g_null_draw_bindings_enabled;
g_null_draw_bindings_enabled = enabled;
return old_value;
}
bool GetNullDrawBindingsEnabled() {
return g_null_draw_bindings_enabled;
}
void SetCurrentGL(CurrentGL* current) {
CurrentGL* new_current = current ? current : g_no_context_current_gl;
g_current_gl_context_tls->Set(new_current);
}
GLApi::GLApi() {
}
GLApi::~GLApi() {
}
GLApiBase::GLApiBase()
: driver_(NULL) {
}
GLApiBase::~GLApiBase() {
}
void GLApiBase::InitializeBase(DriverGL* driver) {
driver_ = driver;
}
RealGLApi::RealGLApi() {
}
RealGLApi::~RealGLApi() {
}
void RealGLApi::Initialize(DriverGL* driver) {
InitializeBase(driver);
}
void RealGLApi::glGetIntegervFn(GLenum pname, GLint* params) {
if (pname == GL_NUM_EXTENSIONS && disabled_exts_.size()) {
InitializeFilteredExtensionsIfNeeded();
*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()) {
InitializeFilteredExtensionsIfNeeded();
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()) {
InitializeFilteredExtensionsIfNeeded();
if (index >= filtered_exts_.size()) {
return nullptr;
}
return reinterpret_cast<const GLubyte*>(filtered_exts_[index].c_str());
}
return GLApiBase::glGetStringiFn(name, index);
}
void RealGLApi::glTexImage2DFn(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
const void* pixels) {
GLenum gl_internal_format =
GetTexInternalFormat(version_.get(), internalformat, format, type);
GLenum gl_format = GetTexFormat(version_.get(), format);
GLenum gl_type = GetPixelType(version_.get(), type, format);
// TODO(yizhou): Check if cubemap, 3d texture or texture2d array has the same
// bug on intel mac.
if (!version_->is_angle && gl_workarounds_.reset_teximage2d_base_level &&
target == GL_TEXTURE_2D) {
GLint base_level = 0;
GLApiBase::glGetTexParameterivFn(target, GL_TEXTURE_BASE_LEVEL,
&base_level);
if (base_level) {
GLApiBase::glTexParameteriFn(target, GL_TEXTURE_BASE_LEVEL, 0);
GLApiBase::glTexImage2DFn(target, level, gl_internal_format, width,
height, border, gl_format, gl_type, pixels);
GLApiBase::glTexParameteriFn(target, GL_TEXTURE_BASE_LEVEL, base_level);
return;
}
}
GLApiBase::glTexImage2DFn(target, level, gl_internal_format, width, height,
border, gl_format, gl_type, pixels);
}
void RealGLApi::glTexSubImage2DFn(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
const void* pixels) {
GLenum gl_format = GetTexFormat(version_.get(), format);
GLenum gl_type = GetPixelType(version_.get(), type, format);
GLApiBase::glTexSubImage2DFn(target, level, xoffset, yoffset, width, height,
gl_format, gl_type, pixels);
}
void RealGLApi::glTexStorage2DEXTFn(GLenum target,
GLsizei levels,
GLenum internalformat,
GLsizei width,
GLsizei height) {
GLenum gl_internal_format = GetInternalFormat(version_.get(), internalformat);
GLApiBase::glTexStorage2DEXTFn(target, levels, gl_internal_format, width,
height);
}
void RealGLApi::glRenderbufferStorageEXTFn(GLenum target,
GLenum internalformat,
GLsizei width,
GLsizei height) {
GLenum gl_internal_format = GetInternalFormat(version_.get(), internalformat);
GLApiBase::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.
void RealGLApi::glRenderbufferStorageMultisampleEXTFn(GLenum target,
GLsizei samples,
GLenum internalformat,
GLsizei width,
GLsizei height) {
GLenum gl_internal_format = GetInternalFormat(version_.get(), internalformat);
GLApiBase::glRenderbufferStorageMultisampleEXTFn(
target, samples, gl_internal_format, width, height);
}
void RealGLApi::glRenderbufferStorageMultisampleFn(GLenum target,
GLsizei samples,
GLenum internalformat,
GLsizei width,
GLsizei height) {
GLenum gl_internal_format = GetInternalFormat(version_.get(), internalformat);
GLApiBase::glRenderbufferStorageMultisampleFn(
target, samples, gl_internal_format, width, height);
}
void RealGLApi::glReadPixelsFn(GLint x,
GLint y,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
void* pixels) {
GLenum gl_type = GetPixelType(version_.get(), type, format);
GLApiBase::glReadPixelsFn(x, y, width, height, format, gl_type, pixels);
}
void RealGLApi::glClearFn(GLbitfield mask) {
if (!g_null_draw_bindings_enabled)
GLApiBase::glClearFn(mask);
}
void RealGLApi::glClearColorFn(GLclampf red,
GLclampf green,
GLclampf blue,
GLclampf alpha) {
if (!version_->is_angle && gl_workarounds_.clear_to_zero_or_one_broken &&
(1 == red || 0 == red) && (1 == green || 0 == green) &&
(1 == blue || 0 == blue) && (1 == alpha || 0 == alpha)) {
if (1 == alpha)
alpha = 2;
else
alpha = -1;
}
GLApiBase::glClearColorFn(red, green, blue, alpha);
}
void RealGLApi::glDrawArraysFn(GLenum mode, GLint first, GLsizei count) {
if (!g_null_draw_bindings_enabled)
GLApiBase::glDrawArraysFn(mode, first, count);
}
void RealGLApi::glDrawElementsFn(GLenum mode,
GLsizei count,
GLenum type,
const void* indices) {
if (!g_null_draw_bindings_enabled)
GLApiBase::glDrawElementsFn(mode, count, type, indices);
}
void RealGLApi::glClearDepthFn(GLclampd depth) {
// OpenGL ES only has glClearDepthf, forward the parameters from glClearDepth.
// Many mock tests expect only glClearDepth is called so don't make the
// interception when testing with mocks.
if (version_->is_es && GetGLImplementation() != kGLImplementationMockGL) {
DCHECK(driver_->fn.glClearDepthfFn);
GLApiBase::glClearDepthfFn(static_cast<GLclampf>(depth));
} else {
DCHECK(driver_->fn.glClearDepthFn);
GLApiBase::glClearDepthFn(depth);
}
}
void RealGLApi::glDepthRangeFn(GLclampd z_near, GLclampd z_far) {
// OpenGL ES only has glDepthRangef, forward the parameters from glDepthRange.
// Many mock tests expect only glDepthRange is called so don't make the
// interception when testing with mocks.
if (version_->is_es && GetGLImplementation() != kGLImplementationMockGL) {
DCHECK(driver_->fn.glDepthRangefFn);
GLApiBase::glDepthRangefFn(static_cast<GLclampf>(z_near),
static_cast<GLclampf>(z_far));
} else {
DCHECK(driver_->fn.glDepthRangeFn);
GLApiBase::glDepthRangeFn(z_near, z_far);
}
}
void RealGLApi::glUseProgramFn(GLuint program) {
ShaderTracking* shader_tracking = ShaderTracking::GetInstance();
if (shader_tracking) {
std::vector<char> buffers[2];
char* strings[2] = {nullptr, nullptr};
if (program) {
// The following only works with ANGLE backend because ANGLE makes sure
// a program's shaders are not actually deleted and source can still be
// queried even if glDeleteShaders() has been called on them.
// Also, in theory, different shaders can be attached to the program
// after the last link, but for now, ignore such corner case patterns.
GLsizei count = 0;
GLuint shaders[2] = {0};
glGetAttachedShadersFn(program, 2, &count, shaders);
for (GLsizei ii = 0; ii < std::min(2, count); ++ii) {
buffers[ii].resize(ShaderTracking::kMaxShaderSize);
glGetShaderSourceFn(shaders[ii], ShaderTracking::kMaxShaderSize,
nullptr, buffers[ii].data());
strings[ii] = buffers[ii].data();
}
}
shader_tracking->SetShaders(strings[0], strings[1]);
}
GLApiBase::glUseProgramFn(program);
}
void RealGLApi::InitializeFilteredExtensionsIfNeeded() {
DCHECK(disabled_exts_.size());
if (filtered_exts_.size())
return;
DCHECK(filtered_exts_str_.empty());
if (WillUseGLGetStringForExtensions(this)) {
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);
if (!base::Contains(disabled_exts_, gl_extension))
filtered_exts_.push_back(gl_extension);
}
filtered_exts_str_ = base::JoinString(filtered_exts_, " ");
}
}
void RealGLApi::SetDisabledExtensions(const std::string& disabled_extensions) {
ClearCachedGLExtensions();
disabled_exts_.clear();
if (disabled_extensions.empty())
return;
disabled_exts_ =
base::SplitString(disabled_extensions, ", ;", base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
DCHECK(disabled_exts_.size());
}
void RealGLApi::ClearCachedGLExtensions() {
filtered_exts_.clear();
filtered_exts_str_.clear();
}
void RealGLApi::set_gl_workarounds(const GLWorkarounds& workarounds) {
gl_workarounds_ = workarounds;
}
void RealGLApi::set_version(std::unique_ptr<GLVersionInfo> version) {
version_ = std::move(version);
}
TraceGLApi::~TraceGLApi() {
}
DebugGLApi::DebugGLApi(GLApi* gl_api) : gl_api_(gl_api) {}
DebugGLApi::~DebugGLApi() {}
NoContextGLApi::NoContextGLApi() {
}
NoContextGLApi::~NoContextGLApi() {
}
} // namespace gl