1202 lines
45 KiB
C++
1202 lines
45 KiB
C++
/*
|
|
* Copyright (C) 2019 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "OpenGLContext.h"
|
|
|
|
#include "GLUtils.h"
|
|
#include "OpenGLTimerQuery.h"
|
|
|
|
#include <backend/platforms/OpenGLPlatform.h>
|
|
#include <backend/DriverEnums.h>
|
|
|
|
#include <utils/compiler.h>
|
|
#include <utils/debug.h>
|
|
#include <utils/Log.h>
|
|
#include <utils/ostream.h>
|
|
|
|
#include <functional>
|
|
#include <string_view>
|
|
#include <utility>
|
|
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
// change to true to display all GL extensions in the console on start-up
|
|
#define DEBUG_PRINT_EXTENSIONS false
|
|
|
|
using namespace utils;
|
|
|
|
namespace filament::backend {
|
|
|
|
bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept {
|
|
#ifdef BACKEND_OPENGL_VERSION_GLES
|
|
# ifdef BACKEND_OPENGL_LEVEL_GLES30
|
|
char const* version = (char const*)glGetString(GL_VERSION);
|
|
// This works on all versions of GLES
|
|
int const n = version ? sscanf(version, "OpenGL ES %d.%d", major, minor) : 0;
|
|
return n == 2;
|
|
# else
|
|
// when we compile with GLES2.0 only, we force the context version to 2.0
|
|
*major = 2;
|
|
*minor = 0;
|
|
return true;
|
|
# endif
|
|
#else
|
|
// OpenGL version
|
|
glGetIntegerv(GL_MAJOR_VERSION, major);
|
|
glGetIntegerv(GL_MINOR_VERSION, minor);
|
|
return (glGetError() == GL_NO_ERROR);
|
|
#endif
|
|
}
|
|
|
|
OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
|
|
Platform::DriverConfig const& driverConfig) noexcept
|
|
: mPlatform(platform),
|
|
mSamplerMap(32),
|
|
mDriverConfig(driverConfig) {
|
|
|
|
state.vao.p = &mDefaultVAO;
|
|
|
|
// These queries work with all GL/GLES versions!
|
|
state.vendor = (char const*)glGetString(GL_VENDOR);
|
|
state.renderer = (char const*)glGetString(GL_RENDERER);
|
|
state.version = (char const*)glGetString(GL_VERSION);
|
|
state.shader = (char const*)glGetString(GL_SHADING_LANGUAGE_VERSION);
|
|
|
|
slog.v << "[" << state.vendor << "], [" << state.renderer << "], "
|
|
"[" << state.version << "], [" << state.shader << "]" << io::endl;
|
|
|
|
/*
|
|
* Figure out GL / GLES version, extensions and capabilities we need to
|
|
* determine the feature level
|
|
*/
|
|
|
|
queryOpenGLVersion(&state.major, &state.minor);
|
|
|
|
#if defined(BACKEND_OPENGL_VERSION_GLES)
|
|
if (UTILS_UNLIKELY(driverConfig.forceGLES2Context)) {
|
|
state.major = 2;
|
|
state.minor = 0;
|
|
}
|
|
#endif
|
|
|
|
OpenGLContext::initExtensions(&ext, state.major, state.minor);
|
|
|
|
OpenGLContext::initProcs(&procs, ext, state.major, state.minor);
|
|
|
|
OpenGLContext::initBugs(&bugs, ext, state.major, state.minor,
|
|
state.vendor, state.renderer, state.version, state.shader);
|
|
|
|
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &gets.max_renderbuffer_size);
|
|
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &gets.max_texture_image_units);
|
|
glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gets.max_combined_texture_image_units);
|
|
|
|
mFeatureLevel = OpenGLContext::resolveFeatureLevel(state.major, state.minor, ext, gets, bugs);
|
|
|
|
#ifdef BACKEND_OPENGL_VERSION_GLES
|
|
mShaderModel = ShaderModel::MOBILE;
|
|
#else
|
|
mShaderModel = ShaderModel::DESKTOP;
|
|
#endif
|
|
|
|
#ifdef BACKEND_OPENGL_VERSION_GLES
|
|
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_2) {
|
|
features.multisample_texture = true;
|
|
}
|
|
#else
|
|
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) {
|
|
features.multisample_texture = true;
|
|
}
|
|
#endif
|
|
|
|
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) {
|
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
#ifdef GL_EXT_texture_filter_anisotropic
|
|
if (ext.EXT_texture_filter_anisotropic) {
|
|
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gets.max_anisotropy);
|
|
}
|
|
#endif
|
|
glGetIntegerv(GL_MAX_DRAW_BUFFERS,
|
|
&gets.max_draw_buffers);
|
|
glGetIntegerv(GL_MAX_SAMPLES,
|
|
&gets.max_samples);
|
|
glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
|
|
&gets.max_transform_feedback_separate_attribs);
|
|
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE,
|
|
&gets.max_uniform_block_size);
|
|
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS,
|
|
&gets.max_uniform_buffer_bindings);
|
|
glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS,
|
|
&gets.num_program_binary_formats);
|
|
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT,
|
|
&gets.uniform_buffer_offset_alignment);
|
|
#endif
|
|
}
|
|
|
|
#ifdef BACKEND_OPENGL_VERSION_GLES
|
|
else {
|
|
gets.max_anisotropy = 1;
|
|
gets.max_draw_buffers = 1;
|
|
gets.max_samples = 1;
|
|
gets.max_transform_feedback_separate_attribs = 0;
|
|
gets.max_uniform_block_size = 0;
|
|
gets.max_uniform_buffer_bindings = 0;
|
|
gets.num_program_binary_formats = 0;
|
|
gets.uniform_buffer_offset_alignment = 0;
|
|
}
|
|
#endif
|
|
|
|
slog.v << "Feature level: " << +mFeatureLevel << '\n';
|
|
slog.v << "Active workarounds: " << '\n';
|
|
UTILS_NOUNROLL
|
|
for (auto [enabled, name, _] : mBugDatabase) {
|
|
if (enabled) {
|
|
slog.v << name << '\n';
|
|
}
|
|
}
|
|
flush(slog.v);
|
|
|
|
#ifndef NDEBUG
|
|
// this is useful for development
|
|
slog.v
|
|
<< "GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT = "
|
|
<< gets.max_anisotropy << '\n'
|
|
<< "GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS = "
|
|
<< gets.max_combined_texture_image_units << '\n'
|
|
<< "GL_MAX_DRAW_BUFFERS = "
|
|
<< gets.max_draw_buffers << '\n'
|
|
<< "GL_MAX_RENDERBUFFER_SIZE = "
|
|
<< gets.max_renderbuffer_size << '\n'
|
|
<< "GL_MAX_SAMPLES = "
|
|
<< gets.max_samples << '\n'
|
|
<< "GL_MAX_TEXTURE_IMAGE_UNITS = "
|
|
<< gets.max_texture_image_units << '\n'
|
|
<< "GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS = "
|
|
<< gets.max_transform_feedback_separate_attribs << '\n'
|
|
<< "GL_MAX_UNIFORM_BLOCK_SIZE = "
|
|
<< gets.max_uniform_block_size << '\n'
|
|
<< "GL_MAX_UNIFORM_BUFFER_BINDINGS = "
|
|
<< gets.max_uniform_buffer_bindings << '\n'
|
|
<< "GL_NUM_PROGRAM_BINARY_FORMATS = "
|
|
<< gets.num_program_binary_formats << '\n'
|
|
<< "GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT = "
|
|
<< gets.uniform_buffer_offset_alignment << '\n'
|
|
;
|
|
flush(slog.v);
|
|
#endif
|
|
|
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
assert_invariant(mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0 || gets.max_draw_buffers >= 4); // minspec
|
|
#endif
|
|
|
|
setDefaultState();
|
|
|
|
#ifdef GL_EXT_texture_filter_anisotropic
|
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1 && ext.EXT_texture_filter_anisotropic) {
|
|
// make sure we don't have any error flag
|
|
while (glGetError() != GL_NO_ERROR) { }
|
|
|
|
// check that we can actually set the anisotropy on the sampler
|
|
GLuint s;
|
|
glGenSamplers(1, &s);
|
|
glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT, gets.max_anisotropy);
|
|
if (glGetError() != GL_NO_ERROR) {
|
|
// some drivers only allow to set the anisotropy on the texture itself
|
|
bugs.texture_filter_anisotropic_broken_on_sampler = true;
|
|
}
|
|
glDeleteSamplers(1, &s);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
// in practice KHR_Debug has never been useful, and actually is confusing. We keep this
|
|
// only for our own debugging, in case we need it some day.
|
|
#if false && !defined(NDEBUG) && defined(GL_KHR_debug)
|
|
if (ext.KHR_debug) {
|
|
auto cb = +[](GLenum, GLenum type, GLuint, GLenum severity, GLsizei length,
|
|
const GLchar* message, const void *) {
|
|
io::ostream* stream = &slog.i;
|
|
switch (severity) {
|
|
case GL_DEBUG_SEVERITY_HIGH: stream = &slog.e; break;
|
|
case GL_DEBUG_SEVERITY_MEDIUM: stream = &slog.w; break;
|
|
case GL_DEBUG_SEVERITY_LOW: stream = &slog.d; break;
|
|
case GL_DEBUG_SEVERITY_NOTIFICATION:
|
|
default: break;
|
|
}
|
|
io::ostream& out = *stream;
|
|
const char* level = ": ";
|
|
switch (type) {
|
|
case GL_DEBUG_TYPE_ERROR: level = "ERROR: "; break;
|
|
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: level = "DEPRECATED_BEHAVIOR: "; break;
|
|
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: level = "UNDEFINED_BEHAVIOR: "; break;
|
|
case GL_DEBUG_TYPE_PORTABILITY: level = "PORTABILITY: "; break;
|
|
case GL_DEBUG_TYPE_PERFORMANCE: level = "PERFORMANCE: "; break;
|
|
case GL_DEBUG_TYPE_OTHER: level = "OTHER: "; break;
|
|
case GL_DEBUG_TYPE_MARKER: level = "MARKER: "; break;
|
|
default: break;
|
|
}
|
|
out << "KHR_debug " << level << std::string_view{ message, size_t(length) } << io::endl;
|
|
};
|
|
glEnable(GL_DEBUG_OUTPUT);
|
|
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
|
|
glDebugMessageCallback(cb, nullptr);
|
|
}
|
|
#endif
|
|
|
|
mTimerQueryFactory = TimerQueryFactory::init(platform, *this);
|
|
}
|
|
|
|
OpenGLContext::~OpenGLContext() noexcept {
|
|
// note: this is called from the main thread. Can't do any GL calls.
|
|
delete mTimerQueryFactory;
|
|
}
|
|
|
|
void OpenGLContext::terminate() noexcept {
|
|
// note: this is called from the backend thread
|
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
if (!isES2()) {
|
|
for (auto& item: mSamplerMap) {
|
|
unbindSampler(item.second);
|
|
glDeleteSamplers(1, &item.second);
|
|
}
|
|
mSamplerMap.clear();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void OpenGLContext::destroyWithContext(
|
|
size_t index, std::function<void(OpenGLContext&)> const& closure) noexcept {
|
|
if (index == 0) {
|
|
// Note: we only need to delay the destruction of objects on the unprotected context
|
|
// (index 0) because the protected context is always immediately destroyed and all its
|
|
// active objects and bindings are then automatically destroyed.
|
|
// TODO: this is only guaranteed for EGLPlatform, but that's the only one we care about.
|
|
mDestroyWithNormalContext.push_back(closure);
|
|
}
|
|
}
|
|
|
|
void OpenGLContext::unbindEverything() noexcept {
|
|
// TODO: we're supposed to unbind everything here so that resources don't get
|
|
// stuck in this context (contextIndex) when destroyed in the other context.
|
|
// However, because EGLPlatform always immediately destroys the protected context (1),
|
|
// the bindings will automatically be severed when we switch back to the default context.
|
|
// Since bindings now only exist in one context, we don't have a ref-counting issue to
|
|
// worry about.
|
|
}
|
|
|
|
void OpenGLContext::synchronizeStateAndCache(size_t index) noexcept {
|
|
|
|
// if we're just switching back to context 0, run all the pending destructors
|
|
if (index == 0) {
|
|
auto list = std::move(mDestroyWithNormalContext);
|
|
for (auto&& fn: list) {
|
|
fn(*this);
|
|
}
|
|
}
|
|
|
|
// the default FBO could be invalid
|
|
mDefaultFbo[index].reset();
|
|
|
|
contextIndex = index;
|
|
resetState();
|
|
}
|
|
|
|
void OpenGLContext::setDefaultState() noexcept {
|
|
// We need to make sure our internal state matches the GL state when we start.
|
|
// (some of these calls may be unneeded as they might be the gl defaults)
|
|
GLenum const caps[] = {
|
|
GL_BLEND,
|
|
GL_CULL_FACE,
|
|
GL_SCISSOR_TEST,
|
|
GL_DEPTH_TEST,
|
|
GL_STENCIL_TEST,
|
|
GL_DITHER,
|
|
GL_SAMPLE_ALPHA_TO_COVERAGE,
|
|
GL_SAMPLE_COVERAGE,
|
|
GL_POLYGON_OFFSET_FILL,
|
|
};
|
|
|
|
UTILS_NOUNROLL
|
|
for (auto const capi : caps) {
|
|
size_t const capIndex = getIndexForCap(capi);
|
|
if (state.enables.caps[capIndex]) {
|
|
glEnable(capi);
|
|
} else {
|
|
glDisable(capi);
|
|
}
|
|
}
|
|
|
|
// Point sprite size and seamless cubemap filtering are disabled by default in desktop GL.
|
|
// In OpenGL ES, these flags do not exist because they are always on.
|
|
#ifdef BACKEND_OPENGL_VERSION_GL
|
|
glEnable(GL_PROGRAM_POINT_SIZE);
|
|
enable(GL_PROGRAM_POINT_SIZE);
|
|
#endif
|
|
|
|
#ifdef GL_ARB_seamless_cube_map
|
|
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
|
|
enable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
|
|
#endif
|
|
|
|
#ifdef GL_FRAGMENT_SHADER_DERIVATIVE_HINT
|
|
glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST);
|
|
#endif
|
|
|
|
#if !defined(__EMSCRIPTEN__)
|
|
if (ext.EXT_clip_control) {
|
|
#if defined(BACKEND_OPENGL_VERSION_GL)
|
|
glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
|
|
#elif defined(GL_EXT_clip_control)
|
|
glClipControlEXT(GL_LOWER_LEFT_EXT, GL_ZERO_TO_ONE_EXT);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
if (ext.EXT_clip_cull_distance
|
|
&& mDriverConfig.stereoscopicType == StereoscopicType::INSTANCED) {
|
|
glEnable(GL_CLIP_DISTANCE0);
|
|
glEnable(GL_CLIP_DISTANCE1);
|
|
}
|
|
}
|
|
|
|
|
|
void OpenGLContext::initProcs(Procs* procs,
|
|
Extensions const& ext, GLint major, GLint) noexcept {
|
|
(void)ext;
|
|
(void)major;
|
|
|
|
// default procs that can be overridden based on runtime version
|
|
#ifdef BACKEND_OPENGL_LEVEL_GLES30
|
|
procs->genVertexArrays = glGenVertexArrays;
|
|
procs->bindVertexArray = glBindVertexArray;
|
|
procs->deleteVertexArrays = glDeleteVertexArrays;
|
|
|
|
// these are core in GL and GLES 3.x
|
|
procs->genQueries = glGenQueries;
|
|
procs->deleteQueries = glDeleteQueries;
|
|
procs->beginQuery = glBeginQuery;
|
|
procs->endQuery = glEndQuery;
|
|
procs->getQueryObjectuiv = glGetQueryObjectuiv;
|
|
# ifdef BACKEND_OPENGL_VERSION_GL
|
|
procs->getQueryObjectui64v = glGetQueryObjectui64v; // only core in GL
|
|
# elif defined(GL_EXT_disjoint_timer_query)
|
|
# ifndef __EMSCRIPTEN__
|
|
procs->getQueryObjectui64v = glGetQueryObjectui64vEXT;
|
|
# endif
|
|
# endif // BACKEND_OPENGL_VERSION_GL
|
|
|
|
// core in ES 3.0 and GL 4.3
|
|
procs->invalidateFramebuffer = glInvalidateFramebuffer;
|
|
#endif // BACKEND_OPENGL_LEVEL_GLES30
|
|
|
|
// no-op if not supported
|
|
procs->maxShaderCompilerThreadsKHR = +[](GLuint) {};
|
|
|
|
#ifdef BACKEND_OPENGL_VERSION_GLES
|
|
# ifndef IOS // IOS is guaranteed to have ES3.x
|
|
# ifndef __EMSCRIPTEN__
|
|
if (UTILS_UNLIKELY(major == 2)) {
|
|
// Runtime OpenGL version is ES 2.x
|
|
if (UTILS_LIKELY(ext.OES_vertex_array_object)) {
|
|
procs->genVertexArrays = glGenVertexArraysOES;
|
|
procs->bindVertexArray = glBindVertexArrayOES;
|
|
procs->deleteVertexArrays = glDeleteVertexArraysOES;
|
|
} else {
|
|
// if we don't have OES_vertex_array_object, just don't do anything with real VAOs,
|
|
// we'll just rebind everything each time. Most Mali-400 support this extension, but
|
|
// a few don't.
|
|
procs->genVertexArrays = +[](GLsizei, GLuint*) {};
|
|
procs->bindVertexArray = +[](GLuint) {};
|
|
procs->deleteVertexArrays = +[](GLsizei, GLuint const*) {};
|
|
}
|
|
|
|
// EXT_disjoint_timer_query is optional -- pointers will be null if not available
|
|
procs->genQueries = glGenQueriesEXT;
|
|
procs->deleteQueries = glDeleteQueriesEXT;
|
|
procs->beginQuery = glBeginQueryEXT;
|
|
procs->endQuery = glEndQueryEXT;
|
|
procs->getQueryObjectuiv = glGetQueryObjectuivEXT;
|
|
procs->getQueryObjectui64v = glGetQueryObjectui64vEXT;
|
|
|
|
procs->invalidateFramebuffer = glDiscardFramebufferEXT;
|
|
|
|
procs->maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsKHR;
|
|
}
|
|
# endif // __EMSCRIPTEN__
|
|
# endif // IOS
|
|
#else
|
|
procs->maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsARB;
|
|
#endif
|
|
}
|
|
|
|
void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
|
|
GLint major, GLint minor,
|
|
char const* vendor,
|
|
char const* renderer,
|
|
char const* version,
|
|
char const* shader) {
|
|
|
|
(void)major;
|
|
(void)minor;
|
|
(void)vendor;
|
|
(void)renderer;
|
|
(void)version;
|
|
(void)shader;
|
|
|
|
const bool isAngle = strstr(renderer, "ANGLE");
|
|
if (!isAngle) {
|
|
if (strstr(renderer, "Adreno")) {
|
|
// Qualcomm GPU
|
|
bugs->invalidate_end_only_if_invalidate_start = true;
|
|
|
|
// On Adreno (As of 3/20) timer query seem to return the CPU time, not the GPU time.
|
|
bugs->dont_use_timer_query = true;
|
|
|
|
// Blits to texture arrays are failing
|
|
// This bug continues to reproduce, though at times we've seen it appear to "go away".
|
|
// The standalone sample app that was written to show this problem still reproduces.
|
|
// The working hypothesis is that some other state affects this behavior.
|
|
bugs->disable_blit_into_texture_array = true;
|
|
|
|
// early exit condition is flattened in EASU code
|
|
bugs->split_easu = true;
|
|
|
|
// initialize the non-used uniform array for Adreno drivers.
|
|
bugs->enable_initialize_non_used_uniform_array = true;
|
|
|
|
int maj, min, driverMajor, driverMinor;
|
|
int const c = sscanf(version, "OpenGL ES %d.%d V@%d.%d", // NOLINT(cert-err34-c)
|
|
&maj, &min, &driverMajor, &driverMinor);
|
|
if (c == 4) {
|
|
// Workarounds based on version here.
|
|
// Notes:
|
|
// bugs.invalidate_end_only_if_invalidate_start
|
|
// - appeared at least in
|
|
// "OpenGL ES 3.2 V@0490.0 (GIT@85da404, I46ff5fc46f, 1606794520) (Date:11/30/20)"
|
|
// - wasn't present in
|
|
// "OpenGL ES 3.2 V@0490.0 (GIT@0905e9f, Ia11ce2d146, 1599072951) (Date:09/02/20)"
|
|
// - has been confirmed fixed in V@570.1 by Qualcomm
|
|
if (driverMajor < 490 || driverMajor > 570 ||
|
|
(driverMajor == 570 && driverMinor >= 1)) {
|
|
bugs->invalidate_end_only_if_invalidate_start = false;
|
|
}
|
|
}
|
|
|
|
// qualcomm seems to have no problem with this (which is good for us)
|
|
bugs->allow_read_only_ancillary_feedback_loop = true;
|
|
|
|
// Older Adreno devices that support ES3.0 only tend to be extremely buggy, so we
|
|
// fall back to ES2.0.
|
|
if (major == 3 && minor == 0) {
|
|
bugs->force_feature_level0 = true;
|
|
}
|
|
} else if (strstr(renderer, "Mali")) {
|
|
// ARM GPU
|
|
bugs->vao_doesnt_store_element_array_buffer_binding = true;
|
|
if (strstr(renderer, "Mali-T")) {
|
|
bugs->disable_glFlush = true;
|
|
bugs->disable_shared_context_draws = true;
|
|
// We have not verified that timer queries work on Mali-T, so we disable to be safe.
|
|
bugs->dont_use_timer_query = true;
|
|
}
|
|
if (strstr(renderer, "Mali-G")) {
|
|
// We have run into several problems with timer queries on Mali-Gxx:
|
|
// - timer queries seem to cause memory corruptions in some cases on some devices
|
|
// (see b/233754398)
|
|
// - appeared at least in: "OpenGL ES 3.2 v1.r26p0-01eac0"
|
|
// - wasn't present in: "OpenGL ES 3.2 v1.r32p1-00pxl1"
|
|
// - timer queries sometime crash with an NPE (see b/273759031)
|
|
bugs->dont_use_timer_query = true;
|
|
}
|
|
// Mali seems to have no problem with this (which is good for us)
|
|
bugs->allow_read_only_ancillary_feedback_loop = true;
|
|
} else if (strstr(renderer, "Intel")) {
|
|
// Intel GPU
|
|
bugs->vao_doesnt_store_element_array_buffer_binding = true;
|
|
} else if (strstr(renderer, "PowerVR")) {
|
|
// PowerVR GPU
|
|
// On PowerVR (Rogue GE8320) glFlush doesn't seem to do anything, in particular,
|
|
// it doesn't kick the GPU earlier, so don't issue these calls as they seem to slow
|
|
// things down.
|
|
bugs->disable_glFlush = true;
|
|
// On PowerVR (Rogue GE8320) using gl_InstanceID too early in the shader doesn't work.
|
|
bugs->powervr_shader_workarounds = true;
|
|
// On PowerVR (Rogue GE8320) destroying a fbo after glBlitFramebuffer is effectively
|
|
// equivalent to glFinish.
|
|
bugs->delay_fbo_destruction = true;
|
|
// PowerVR seems to have no problem with this (which is good for us)
|
|
bugs->allow_read_only_ancillary_feedback_loop = true;
|
|
} else if (strstr(renderer, "Apple")) {
|
|
// Apple GPU
|
|
} else if (strstr(renderer, "Tegra") ||
|
|
strstr(renderer, "GeForce") ||
|
|
strstr(renderer, "NV")) {
|
|
// NVIDIA GPU
|
|
} else if (strstr(renderer, "Vivante")) {
|
|
// Vivante GPU
|
|
} else if (strstr(renderer, "AMD") ||
|
|
strstr(renderer, "ATI")) {
|
|
// AMD/ATI GPU
|
|
} else if (strstr(renderer, "Mozilla")) {
|
|
bugs->disable_invalidate_framebuffer = true;
|
|
}
|
|
} else {
|
|
// When running under ANGLE, it's a different set of workaround that we need.
|
|
if (strstr(renderer, "Adreno")) {
|
|
// Qualcomm GPU
|
|
// early exit condition is flattened in EASU code
|
|
// (that should be regardless of ANGLE, but we should double-check)
|
|
bugs->split_easu = true;
|
|
}
|
|
}
|
|
|
|
#ifdef BACKEND_OPENGL_VERSION_GLES
|
|
# ifndef IOS // IOS is guaranteed to have ES3.x
|
|
if (UTILS_UNLIKELY(major == 2)) {
|
|
if (UTILS_UNLIKELY(!exts.OES_vertex_array_object)) {
|
|
// we activate this workaround path, which does the reset of array buffer
|
|
bugs->vao_doesnt_store_element_array_buffer_binding = true;
|
|
}
|
|
}
|
|
# endif // IOS
|
|
#else
|
|
// feedback loops are allowed on GL desktop as long as writes are disabled
|
|
bugs->allow_read_only_ancillary_feedback_loop = true;
|
|
#endif
|
|
}
|
|
|
|
FeatureLevel OpenGLContext::resolveFeatureLevel(GLint major, GLint minor,
|
|
Extensions const& exts,
|
|
Gets const& gets,
|
|
Bugs const& bugs) noexcept {
|
|
|
|
constexpr auto const caps3 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_3];
|
|
constexpr GLint MAX_VERTEX_SAMPLER_COUNT = caps3.MAX_VERTEX_SAMPLER_COUNT;
|
|
constexpr GLint MAX_FRAGMENT_SAMPLER_COUNT = caps3.MAX_FRAGMENT_SAMPLER_COUNT;
|
|
|
|
(void)exts;
|
|
(void)gets;
|
|
(void)bugs;
|
|
|
|
FeatureLevel featureLevel = FeatureLevel::FEATURE_LEVEL_1;
|
|
|
|
#ifdef BACKEND_OPENGL_VERSION_GLES
|
|
if (major == 3) {
|
|
// Runtime OpenGL version is ES 3.x
|
|
assert_invariant(gets.max_texture_image_units >= 16);
|
|
assert_invariant(gets.max_combined_texture_image_units >= 32);
|
|
if (minor >= 1) {
|
|
// figure out our feature level
|
|
if (exts.EXT_texture_cube_map_array) {
|
|
featureLevel = FeatureLevel::FEATURE_LEVEL_2;
|
|
if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
|
|
gets.max_combined_texture_image_units >=
|
|
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
|
|
featureLevel = FeatureLevel::FEATURE_LEVEL_3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
# ifndef IOS // IOS is guaranteed to have ES3.x
|
|
else if (UTILS_UNLIKELY(major == 2)) {
|
|
// Runtime OpenGL version is ES 2.x
|
|
# if defined(BACKEND_OPENGL_LEVEL_GLES30)
|
|
// mandatory extensions (all supported by Mali-400 and Adreno 304)
|
|
assert_invariant(exts.OES_depth_texture);
|
|
assert_invariant(exts.OES_depth24);
|
|
assert_invariant(exts.OES_packed_depth_stencil);
|
|
assert_invariant(exts.OES_rgb8_rgba8);
|
|
assert_invariant(exts.OES_standard_derivatives);
|
|
assert_invariant(exts.OES_texture_npot);
|
|
# endif
|
|
featureLevel = FeatureLevel::FEATURE_LEVEL_0;
|
|
}
|
|
# endif // IOS
|
|
#else
|
|
assert_invariant(gets.max_texture_image_units >= 16);
|
|
assert_invariant(gets.max_combined_texture_image_units >= 32);
|
|
if (major == 4) {
|
|
assert_invariant(minor >= 1);
|
|
if (minor >= 3) {
|
|
// cubemap arrays are available as of OpenGL 4.0
|
|
featureLevel = FeatureLevel::FEATURE_LEVEL_2;
|
|
// figure out our feature level
|
|
if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
|
|
gets.max_combined_texture_image_units >=
|
|
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
|
|
featureLevel = FeatureLevel::FEATURE_LEVEL_3;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (bugs.force_feature_level0) {
|
|
featureLevel = FeatureLevel::FEATURE_LEVEL_0;
|
|
}
|
|
|
|
return featureLevel;
|
|
}
|
|
|
|
#ifdef BACKEND_OPENGL_VERSION_GLES
|
|
|
|
void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor) noexcept {
|
|
const char * const extensions = (const char*)glGetString(GL_EXTENSIONS);
|
|
GLUtils::unordered_string_set const exts = GLUtils::split(extensions);
|
|
if constexpr (DEBUG_PRINT_EXTENSIONS) {
|
|
for (auto extension: exts) {
|
|
slog.d << "\"" << std::string_view(extension) << "\"\n";
|
|
}
|
|
flush(slog.d);
|
|
}
|
|
|
|
// figure out and initialize the extensions we need
|
|
using namespace std::literals;
|
|
ext->APPLE_color_buffer_packed_float = exts.has("GL_APPLE_color_buffer_packed_float"sv);
|
|
#ifndef __EMSCRIPTEN__
|
|
ext->EXT_clip_control = exts.has("GL_EXT_clip_control"sv);
|
|
#endif
|
|
ext->EXT_clip_cull_distance = exts.has("GL_EXT_clip_cull_distance"sv);
|
|
ext->EXT_color_buffer_float = exts.has("GL_EXT_color_buffer_float"sv);
|
|
ext->EXT_color_buffer_half_float = exts.has("GL_EXT_color_buffer_half_float"sv);
|
|
#ifndef __EMSCRIPTEN__
|
|
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
|
#endif
|
|
ext->EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
|
|
#ifndef __EMSCRIPTEN__
|
|
ext->EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
|
|
ext->EXT_multisampled_render_to_texture = exts.has("GL_EXT_multisampled_render_to_texture"sv);
|
|
ext->EXT_multisampled_render_to_texture2 = exts.has("GL_EXT_multisampled_render_to_texture2"sv);
|
|
ext->EXT_protected_textures = exts.has("GL_EXT_protected_textures"sv);
|
|
#endif
|
|
ext->EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
|
|
#ifndef __EMSCRIPTEN__
|
|
ext->EXT_texture_compression_etc2 = true;
|
|
#endif
|
|
ext->EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
|
|
ext->EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
|
|
ext->EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
|
|
ext->EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
|
|
ext->EXT_texture_cube_map_array = exts.has("GL_EXT_texture_cube_map_array"sv) || exts.has("GL_OES_texture_cube_map_array"sv);
|
|
ext->GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
|
|
ext->KHR_debug = exts.has("GL_KHR_debug"sv);
|
|
ext->KHR_parallel_shader_compile = exts.has("GL_KHR_parallel_shader_compile"sv);
|
|
ext->KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
|
|
ext->KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
|
|
ext->OES_depth_texture = exts.has("GL_OES_depth_texture"sv);
|
|
ext->OES_depth24 = exts.has("GL_OES_depth24"sv);
|
|
ext->OES_packed_depth_stencil = exts.has("GL_OES_packed_depth_stencil"sv);
|
|
ext->OES_EGL_image_external_essl3 = exts.has("GL_OES_EGL_image_external_essl3"sv);
|
|
ext->OES_rgb8_rgba8 = exts.has("GL_OES_rgb8_rgba8"sv);
|
|
ext->OES_standard_derivatives = exts.has("GL_OES_standard_derivatives"sv);
|
|
ext->OES_texture_npot = exts.has("GL_OES_texture_npot"sv);
|
|
ext->OES_vertex_array_object = exts.has("GL_OES_vertex_array_object"sv);
|
|
ext->OVR_multiview2 = exts.has("GL_OVR_multiview2"sv);
|
|
ext->WEBGL_compressed_texture_etc = exts.has("WEBGL_compressed_texture_etc"sv);
|
|
ext->WEBGL_compressed_texture_s3tc = exts.has("WEBGL_compressed_texture_s3tc"sv);
|
|
ext->WEBGL_compressed_texture_s3tc_srgb = exts.has("WEBGL_compressed_texture_s3tc_srgb"sv);
|
|
|
|
// ES 3.2 implies EXT_color_buffer_float
|
|
if (major > 3 || (major == 3 && minor >= 2)) {
|
|
ext->EXT_color_buffer_float = true;
|
|
}
|
|
// ES 3.x implies EXT_discard_framebuffer and OES_vertex_array_object
|
|
if (major >= 3) {
|
|
ext->EXT_discard_framebuffer = true;
|
|
ext->OES_vertex_array_object = true;
|
|
}
|
|
}
|
|
|
|
#endif // BACKEND_OPENGL_VERSION_GLES
|
|
|
|
#ifdef BACKEND_OPENGL_VERSION_GL
|
|
|
|
void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor) noexcept {
|
|
GLUtils::unordered_string_set exts;
|
|
GLint n = 0;
|
|
glGetIntegerv(GL_NUM_EXTENSIONS, &n);
|
|
for (GLint i = 0; i < n; i++) {
|
|
exts.emplace((const char*)glGetStringi(GL_EXTENSIONS, (GLuint)i));
|
|
}
|
|
if constexpr (DEBUG_PRINT_EXTENSIONS) {
|
|
for (auto extension: exts) {
|
|
slog.d << "\"" << std::string_view(extension) << "\"\n";
|
|
}
|
|
flush(slog.d);
|
|
}
|
|
|
|
using namespace std::literals;
|
|
ext->APPLE_color_buffer_packed_float = true; // Assumes core profile.
|
|
ext->ARB_shading_language_packing = exts.has("GL_ARB_shading_language_packing"sv);
|
|
ext->EXT_color_buffer_float = true; // Assumes core profile.
|
|
ext->EXT_color_buffer_half_float = true; // Assumes core profile.
|
|
ext->EXT_clip_cull_distance = true;
|
|
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
|
ext->EXT_discard_framebuffer = false;
|
|
ext->EXT_disjoint_timer_query = true;
|
|
ext->EXT_multisampled_render_to_texture = false;
|
|
ext->EXT_multisampled_render_to_texture2 = false;
|
|
ext->EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
|
|
ext->EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
|
|
ext->EXT_texture_compression_etc2 = exts.has("GL_ARB_ES3_compatibility"sv);
|
|
ext->EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
|
|
ext->EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
|
|
ext->EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
|
|
ext->EXT_texture_cube_map_array = true;
|
|
ext->EXT_texture_filter_anisotropic = exts.has("GL_EXT_texture_filter_anisotropic"sv);
|
|
ext->EXT_texture_sRGB = exts.has("GL_EXT_texture_sRGB"sv);
|
|
ext->GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
|
|
ext->KHR_parallel_shader_compile = exts.has("GL_KHR_parallel_shader_compile"sv);
|
|
ext->KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
|
|
ext->KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
|
|
ext->OES_depth_texture = true;
|
|
ext->OES_depth24 = true;
|
|
ext->OES_EGL_image_external_essl3 = false;
|
|
ext->OES_rgb8_rgba8 = true;
|
|
ext->OES_standard_derivatives = true;
|
|
ext->OES_texture_npot = true;
|
|
ext->OES_vertex_array_object = true;
|
|
ext->OVR_multiview2 = exts.has("GL_OVR_multiview2"sv);
|
|
ext->WEBGL_compressed_texture_etc = false;
|
|
ext->WEBGL_compressed_texture_s3tc = false;
|
|
ext->WEBGL_compressed_texture_s3tc_srgb = false;
|
|
|
|
// OpenGL 4.2 implies ARB_shading_language_packing
|
|
if (major > 4 || (major == 4 && minor >= 2)) {
|
|
ext->ARB_shading_language_packing = true;
|
|
}
|
|
// OpenGL 4.3 implies EXT_discard_framebuffer
|
|
if (major > 4 || (major == 4 && minor >= 3)) {
|
|
ext->EXT_discard_framebuffer = true;
|
|
ext->KHR_debug = true;
|
|
}
|
|
// OpenGL 4.5 implies EXT_clip_control
|
|
if (major > 4 || (major == 4 && minor >= 5)) {
|
|
ext->EXT_clip_control = true;
|
|
}
|
|
}
|
|
|
|
#endif // BACKEND_OPENGL_VERSION_GL
|
|
|
|
|
|
GLuint OpenGLContext::bindFramebuffer(GLenum target, GLuint buffer) noexcept {
|
|
if (UTILS_UNLIKELY(buffer == 0)) {
|
|
// we're binding the default frame buffer, resolve its actual name
|
|
auto& defaultFboForThisContext = mDefaultFbo[contextIndex];
|
|
if (UTILS_UNLIKELY(!defaultFboForThisContext.has_value())) {
|
|
defaultFboForThisContext = GLuint(mPlatform.getDefaultFramebufferObject());
|
|
}
|
|
buffer = defaultFboForThisContext.value();
|
|
}
|
|
bindFramebufferResolved(target, buffer);
|
|
return buffer;
|
|
}
|
|
|
|
void OpenGLContext::unbindFramebuffer(GLenum target) noexcept {
|
|
bindFramebufferResolved(target, 0);
|
|
}
|
|
|
|
void OpenGLContext::bindFramebufferResolved(GLenum target, GLuint buffer) noexcept {
|
|
switch (target) {
|
|
case GL_FRAMEBUFFER:
|
|
if (state.draw_fbo != buffer || state.read_fbo != buffer) {
|
|
state.draw_fbo = state.read_fbo = buffer;
|
|
glBindFramebuffer(target, buffer);
|
|
}
|
|
break;
|
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
case GL_DRAW_FRAMEBUFFER:
|
|
if (state.draw_fbo != buffer) {
|
|
state.draw_fbo = buffer;
|
|
glBindFramebuffer(target, buffer);
|
|
}
|
|
break;
|
|
case GL_READ_FRAMEBUFFER:
|
|
if (state.read_fbo != buffer) {
|
|
state.read_fbo = buffer;
|
|
glBindFramebuffer(target, buffer);
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void OpenGLContext::bindBuffer(GLenum target, GLuint buffer) noexcept {
|
|
if (target == GL_ELEMENT_ARRAY_BUFFER) {
|
|
constexpr size_t targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER);
|
|
// GL_ELEMENT_ARRAY_BUFFER is a special case, where the currently bound VAO remembers
|
|
// the index buffer, unless there are no VAO bound (see: bindVertexArray)
|
|
assert_invariant(state.vao.p);
|
|
if (state.buffers.genericBinding[targetIndex] != buffer
|
|
|| ((state.vao.p != &mDefaultVAO) && (state.vao.p->elementArray != buffer))) {
|
|
state.buffers.genericBinding[targetIndex] = buffer;
|
|
if (state.vao.p != &mDefaultVAO) {
|
|
state.vao.p->elementArray = buffer;
|
|
}
|
|
glBindBuffer(target, buffer);
|
|
}
|
|
} else {
|
|
size_t const targetIndex = getIndexForBufferTarget(target);
|
|
update_state(state.buffers.genericBinding[targetIndex], buffer, [&]() {
|
|
glBindBuffer(target, buffer);
|
|
});
|
|
}
|
|
}
|
|
|
|
void OpenGLContext::pixelStore(GLenum pname, GLint param) noexcept {
|
|
GLint* pcur;
|
|
|
|
// Note: GL_UNPACK_SKIP_PIXELS, GL_UNPACK_SKIP_ROWS and
|
|
// GL_PACK_SKIP_PIXELS, GL_PACK_SKIP_ROWS
|
|
// are actually provided as conveniences to the programmer; they provide no functionality
|
|
// that cannot be duplicated at the call site (e.g. glTexImage2D or glReadPixels)
|
|
|
|
switch (pname) {
|
|
case GL_PACK_ALIGNMENT:
|
|
pcur = &state.pack.alignment;
|
|
break;
|
|
case GL_UNPACK_ALIGNMENT:
|
|
pcur = &state.unpack.alignment;
|
|
break;
|
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
case GL_UNPACK_ROW_LENGTH:
|
|
assert_invariant(state.major > 2);
|
|
pcur = &state.unpack.row_length;
|
|
break;
|
|
#endif
|
|
default:
|
|
goto default_case;
|
|
}
|
|
|
|
if (UTILS_UNLIKELY(*pcur != param)) {
|
|
*pcur = param;
|
|
default_case:
|
|
glPixelStorei(pname, param);
|
|
}
|
|
}
|
|
|
|
void OpenGLContext::unbindTexture(
|
|
UTILS_UNUSED_IN_RELEASE GLenum target, GLuint texture_id) noexcept {
|
|
// unbind this texture from all the units it might be bound to
|
|
// no need unbind the texture from FBOs because we're not tracking that state (and there is
|
|
// no need to).
|
|
// Never attempt to unbind texture 0. This could happen with external textures w/ streaming if
|
|
// never populated.
|
|
if (texture_id) {
|
|
UTILS_NOUNROLL
|
|
for (GLuint unit = 0; unit < MAX_TEXTURE_UNIT_COUNT; unit++) {
|
|
if (state.textures.units[unit].id == texture_id) {
|
|
// if this texture is bound, it should be at the same target
|
|
assert_invariant(state.textures.units[unit].target == target);
|
|
unbindTextureUnit(unit);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpenGLContext::unbindTextureUnit(GLuint unit) noexcept {
|
|
update_state(state.textures.units[unit].id, 0u, [&]() {
|
|
activeTexture(unit);
|
|
glBindTexture(state.textures.units[unit].target, 0u);
|
|
});
|
|
}
|
|
|
|
void OpenGLContext::unbindSampler(GLuint sampler) noexcept {
|
|
// unbind this sampler from all the units it might be bound to
|
|
UTILS_NOUNROLL // clang generates >800B of code!!!
|
|
for (GLuint unit = 0; unit < MAX_TEXTURE_UNIT_COUNT; unit++) {
|
|
if (state.textures.units[unit].sampler == sampler) {
|
|
bindSampler(unit, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept {
|
|
glDeleteBuffers(n, buffers);
|
|
// bindings of bound buffers are reset to 0
|
|
const size_t targetIndex = getIndexForBufferTarget(target);
|
|
auto& genericBuffer = state.buffers.genericBinding[targetIndex];
|
|
UTILS_NOUNROLL
|
|
for (GLsizei i = 0; i < n; ++i) {
|
|
if (genericBuffer == buffers[i]) {
|
|
genericBuffer = 0;
|
|
}
|
|
}
|
|
|
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
assert_invariant(mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1 ||
|
|
(target != GL_UNIFORM_BUFFER && target != GL_TRANSFORM_FEEDBACK_BUFFER));
|
|
|
|
if (target == GL_UNIFORM_BUFFER || target == GL_TRANSFORM_FEEDBACK_BUFFER) {
|
|
auto& indexedBuffer = state.buffers.targets[targetIndex];
|
|
UTILS_NOUNROLL // clang generates >1 KiB of code!!
|
|
for (GLsizei i = 0; i < n; ++i) {
|
|
UTILS_NOUNROLL
|
|
for (auto& buffer : indexedBuffer.buffers) {
|
|
if (buffer.name == buffers[i]) {
|
|
buffer.name = 0;
|
|
buffer.offset = 0;
|
|
buffer.size = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void OpenGLContext::deleteVertexArray(GLuint vao) noexcept {
|
|
if (UTILS_LIKELY(vao)) {
|
|
procs.deleteVertexArrays(1, &vao);
|
|
// if the destroyed VAO is bound, clear the binding.
|
|
if (state.vao.p->vao[contextIndex] == vao) {
|
|
bindVertexArray(nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
GLuint OpenGLContext::getSamplerSlow(SamplerParams params) const noexcept {
|
|
assert_invariant(mSamplerMap.find(params) == mSamplerMap.end());
|
|
|
|
using namespace GLUtils;
|
|
|
|
GLuint s;
|
|
glGenSamplers(1, &s);
|
|
glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, (GLint)getTextureFilter(params.filterMin));
|
|
glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, (GLint)getTextureFilter(params.filterMag));
|
|
glSamplerParameteri(s, GL_TEXTURE_WRAP_S, (GLint)getWrapMode(params.wrapS));
|
|
glSamplerParameteri(s, GL_TEXTURE_WRAP_T, (GLint)getWrapMode(params.wrapT));
|
|
glSamplerParameteri(s, GL_TEXTURE_WRAP_R, (GLint)getWrapMode(params.wrapR));
|
|
glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, (GLint)getTextureCompareMode(params.compareMode));
|
|
glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC, (GLint)getTextureCompareFunc(params.compareFunc));
|
|
|
|
#if defined(GL_EXT_texture_filter_anisotropic)
|
|
if (ext.EXT_texture_filter_anisotropic &&
|
|
!bugs.texture_filter_anisotropic_broken_on_sampler) {
|
|
GLfloat const anisotropy = float(1u << params.anisotropyLog2);
|
|
glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT,
|
|
std::min(gets.max_anisotropy, anisotropy));
|
|
}
|
|
#endif
|
|
CHECK_GL_ERROR(utils::slog.e)
|
|
mSamplerMap[params] = s;
|
|
return s;
|
|
}
|
|
#endif
|
|
|
|
|
|
void OpenGLContext::resetState() noexcept {
|
|
// Force GL state to match the Filament state
|
|
|
|
// increase the state version so other parts of the state know to reset
|
|
state.age++;
|
|
|
|
if (state.major > 2) {
|
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, state.draw_fbo);
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, state.read_fbo);
|
|
#endif
|
|
} else {
|
|
assert_invariant(state.read_fbo == state.draw_fbo);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, state.draw_fbo);
|
|
state.read_fbo = state.draw_fbo;
|
|
}
|
|
|
|
|
|
// state.program
|
|
glUseProgram(state.program.use);
|
|
|
|
// state.vao
|
|
state.vao.p = nullptr;
|
|
bindVertexArray(nullptr);
|
|
|
|
// state.raster
|
|
glFrontFace(state.raster.frontFace);
|
|
glCullFace(state.raster.cullFace);
|
|
glBlendEquationSeparate(state.raster.blendEquationRGB, state.raster.blendEquationA);
|
|
glBlendFuncSeparate(
|
|
state.raster.blendFunctionSrcRGB,
|
|
state.raster.blendFunctionDstRGB,
|
|
state.raster.blendFunctionSrcA,
|
|
state.raster.blendFunctionDstA
|
|
);
|
|
glColorMask(
|
|
state.raster.colorMask,
|
|
state.raster.colorMask,
|
|
state.raster.colorMask,
|
|
state.raster.colorMask
|
|
);
|
|
glDepthMask(state.raster.depthMask);
|
|
glDepthFunc(state.raster.depthFunc);
|
|
|
|
// state.stencil
|
|
glStencilFuncSeparate(
|
|
GL_FRONT,
|
|
state.stencil.front.func.func,
|
|
state.stencil.front.func.ref,
|
|
state.stencil.front.func.mask
|
|
);
|
|
glStencilFuncSeparate(
|
|
GL_BACK,
|
|
state.stencil.back.func.func,
|
|
state.stencil.back.func.ref,
|
|
state.stencil.back.func.mask
|
|
);
|
|
glStencilOpSeparate(
|
|
GL_FRONT,
|
|
state.stencil.front.op.sfail,
|
|
state.stencil.front.op.dpfail,
|
|
state.stencil.front.op.dppass
|
|
);
|
|
glStencilOpSeparate(
|
|
GL_BACK,
|
|
state.stencil.back.op.sfail,
|
|
state.stencil.back.op.dpfail,
|
|
state.stencil.back.op.dppass
|
|
);
|
|
glStencilMaskSeparate(GL_FRONT, state.stencil.front.stencilMask);
|
|
glStencilMaskSeparate(GL_BACK, state.stencil.back.stencilMask);
|
|
|
|
// state.polygonOffset
|
|
glPolygonOffset(state.polygonOffset.factor, state.polygonOffset.units);
|
|
|
|
// state.enables
|
|
setDefaultState();
|
|
|
|
// state.buffers
|
|
// Reset state.buffers to its default state to avoid the complexity and error-prone
|
|
// nature of resetting the GL state to its existing state
|
|
state.buffers = {};
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
|
|
if (state.major > 2) {
|
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
for (auto const target: {
|
|
GL_UNIFORM_BUFFER,
|
|
GL_TRANSFORM_FEEDBACK_BUFFER,
|
|
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
|
|
GL_SHADER_STORAGE_BUFFER,
|
|
#endif
|
|
GL_PIXEL_PACK_BUFFER,
|
|
GL_PIXEL_UNPACK_BUFFER,
|
|
}) {
|
|
glBindBuffer(target, 0);
|
|
}
|
|
|
|
for (size_t bufferIndex = 0; bufferIndex < MAX_BUFFER_BINDINGS; ++bufferIndex) {
|
|
if (bufferIndex < (size_t)gets.max_uniform_buffer_bindings) {
|
|
glBindBufferBase(GL_UNIFORM_BUFFER, bufferIndex, 0);
|
|
}
|
|
|
|
if (bufferIndex < (size_t)gets.max_transform_feedback_separate_attribs) {
|
|
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, bufferIndex, 0);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// state.textures
|
|
// Reset state.textures to its default state to avoid the complexity and error-prone
|
|
// nature of resetting the GL state to its existing state
|
|
state.textures = {};
|
|
const std::pair<GLuint, bool> textureTargets[] = {
|
|
{ GL_TEXTURE_2D, true },
|
|
{ GL_TEXTURE_2D_ARRAY, true },
|
|
{ GL_TEXTURE_CUBE_MAP, true },
|
|
{ GL_TEXTURE_3D, true },
|
|
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
|
|
{ GL_TEXTURE_2D_MULTISAMPLE, true },
|
|
#endif
|
|
#if !defined(__EMSCRIPTEN__)
|
|
#if defined(GL_OES_EGL_image_external)
|
|
{ GL_TEXTURE_EXTERNAL_OES, ext.OES_EGL_image_external_essl3 },
|
|
#endif
|
|
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_texture_cube_map_array)
|
|
{ GL_TEXTURE_CUBE_MAP_ARRAY, ext.EXT_texture_cube_map_array },
|
|
#endif
|
|
#endif
|
|
};
|
|
for (GLint unit = 0; unit < gets.max_combined_texture_image_units; ++unit) {
|
|
glActiveTexture(GL_TEXTURE0 + unit);
|
|
if (state.major > 2) {
|
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
glBindSampler(unit, 0);
|
|
#endif
|
|
}
|
|
for (auto [target, available] : textureTargets) {
|
|
if (available) {
|
|
glBindTexture(target, 0);
|
|
}
|
|
}
|
|
}
|
|
glActiveTexture(GL_TEXTURE0 + state.textures.active);
|
|
|
|
// state.unpack
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, state.unpack.alignment);
|
|
if (state.major > 2) {
|
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, state.unpack.row_length);
|
|
#endif
|
|
}
|
|
|
|
|
|
// state.pack
|
|
glPixelStorei(GL_PACK_ALIGNMENT, state.pack.alignment);
|
|
if (state.major > 2) {
|
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
glPixelStorei(GL_PACK_ROW_LENGTH, 0); // we rely on GL_PACK_ROW_LENGTH being zero
|
|
#endif
|
|
}
|
|
|
|
// state.window
|
|
glScissor(
|
|
state.window.scissor.x,
|
|
state.window.scissor.y,
|
|
state.window.scissor.z,
|
|
state.window.scissor.w
|
|
);
|
|
glViewport(
|
|
state.window.viewport.x,
|
|
state.window.viewport.y,
|
|
state.window.viewport.z,
|
|
state.window.viewport.w
|
|
);
|
|
glDepthRangef(state.window.depthRange.x, state.window.depthRange.y);
|
|
}
|
|
|
|
void OpenGLContext::createTimerQuery(GLTimerQuery* query) {
|
|
mTimerQueryFactory->createTimerQuery(query);
|
|
}
|
|
|
|
void OpenGLContext::destroyTimerQuery(GLTimerQuery* query) {
|
|
mTimerQueryFactory->destroyTimerQuery(query);
|
|
}
|
|
|
|
void OpenGLContext::beginTimeElapsedQuery(GLTimerQuery* query) {
|
|
mTimerQueryFactory->beginTimeElapsedQuery(query);
|
|
}
|
|
|
|
void OpenGLContext::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) {
|
|
mTimerQueryFactory->endTimeElapsedQuery(driver, query);
|
|
}
|
|
|
|
} // namesapce filament
|