
ANGLE handles these workarounds internally and some of them generate GL errors when running on top of an ES driver. BUG=angleproject:3672 BUG=angleproject:3671 Change-Id: Ic301f57f882247db1bbe8991647b12b9bb0b4f85 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1697530 Reviewed-by: Zhenyao Mo <zmo@chromium.org> Commit-Queue: Geoff Lang <geofflang@chromium.org> Cr-Commit-Position: refs/heads/master@{#677048}
562 lines
18 KiB
C++
562 lines
18 KiB
C++
// 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
|