diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 5e0ae99b47..b20728c282 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -86,6 +86,8 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3) src/opengl/OpenGLBlobCache.h src/opengl/OpenGLContext.cpp src/opengl/OpenGLContext.h + src/opengl/OpenGLState.cpp + src/opengl/OpenGLState.h src/opengl/OpenGLDriver.cpp src/opengl/OpenGLDriver.h src/opengl/OpenGLDriverFactory.h diff --git a/filament/backend/src/opengl/GLDescriptorSet.cpp b/filament/backend/src/opengl/GLDescriptorSet.cpp index 7d35500cfc..0798cf6fe5 100644 --- a/filament/backend/src/opengl/GLDescriptorSet.cpp +++ b/filament/backend/src/opengl/GLDescriptorSet.cpp @@ -21,8 +21,8 @@ #include "GLTexture.h" #include "GLUtils.h" #include "OpenGLDriver.h" -#include "OpenGLContext.h" #include "OpenGLProgram.h" +#include "OpenGLState.h" #include "gl_headers.h" @@ -49,7 +49,7 @@ namespace filament::backend { -GLDescriptorSet::GLDescriptorSet(OpenGLContext& gl, DescriptorSetLayoutHandle dslh, +GLDescriptorSet::GLDescriptorSet(OpenGLState& gl, DescriptorSetLayoutHandle dslh, GLDescriptorSetLayout const* layout) : descriptors(layout->maxDescriptorBinding + 1), dslh(std::move(dslh)) { @@ -142,7 +142,7 @@ GLDescriptorSet::GLDescriptorSet(OpenGLContext& gl, DescriptorSetLayoutHandle ds } } -void GLDescriptorSet::update(OpenGLContext&, +void GLDescriptorSet::update(OpenGLState&, descriptor_binding_t binding, GLBufferObject* bo, size_t offset, size_t size) noexcept { assert_invariant(binding < descriptors.size()); std::visit([=](auto&& arg) { @@ -164,7 +164,7 @@ void GLDescriptorSet::update(OpenGLContext&, }, descriptors[binding].desc); } -void GLDescriptorSet::update(OpenGLContext& gl, HandleAllocatorGL& handleAllocator, +void GLDescriptorSet::update(OpenGLState& gl, HandleAllocatorGL& handleAllocator, descriptor_binding_t binding, TextureHandle th, SamplerParams params) noexcept { GLTexture* t = th ? handleAllocator.handle_cast(th) : nullptr; @@ -223,7 +223,7 @@ void GLDescriptorSet::update(OpenGLContext& gl, HandleAllocatorGL& handleAllocat }, descriptors[binding].desc); } -void GLDescriptorSet::updateTextureView(OpenGLContext& gl, +void GLDescriptorSet::updateTextureView(OpenGLState& gl, HandleAllocatorGL& handleAllocator, GLuint unit, GLTexture const* t) noexcept { // The common case is that we don't have a ref handle (we only have one if // the texture ever had a View on it). @@ -260,7 +260,7 @@ void GLDescriptorSet::updateTextureView(OpenGLContext& gl, } void GLDescriptorSet::bind( - OpenGLContext& gl, + OpenGLState& gl, HandleAllocatorGL& handleAllocator, OpenGLProgram const& p, descriptor_set_t set, uint32_t const* offsets, bool offsetsOnly) const noexcept { @@ -378,7 +378,7 @@ void GLDescriptorSet::bind( } } #endif - + glTexParameteri(t->gl.target, GL_TEXTURE_MIN_FILTER, (GLint)GLUtils::getTextureFilter(params.filterMin)); glTexParameteri(t->gl.target, GL_TEXTURE_MAG_FILTER, diff --git a/filament/backend/src/opengl/GLDescriptorSet.h b/filament/backend/src/opengl/GLDescriptorSet.h index 0770ae8ffc..292f595721 100644 --- a/filament/backend/src/opengl/GLDescriptorSet.h +++ b/filament/backend/src/opengl/GLDescriptorSet.h @@ -44,27 +44,27 @@ struct GLTexture; struct GLTextureRef; struct GLDescriptorSetLayout; class OpenGLProgram; -class OpenGLContext; class OpenGLDriver; +class OpenGLState; struct GLDescriptorSet : public HwDescriptorSet { using HwDescriptorSet::HwDescriptorSet; - GLDescriptorSet(OpenGLContext& gl, DescriptorSetLayoutHandle dslh, + GLDescriptorSet(OpenGLState& gl, DescriptorSetLayoutHandle dslh, GLDescriptorSetLayout const* layout); // update a buffer descriptor in the set - void update(OpenGLContext& gl, + void update(OpenGLState& gl, descriptor_binding_t binding, GLBufferObject* bo, size_t offset, size_t size) noexcept; // update a sampler descriptor in the set - void update(OpenGLContext& gl, HandleAllocatorGL& handleAllocator, + void update(OpenGLState& gl, HandleAllocatorGL& handleAllocator, descriptor_binding_t binding, TextureHandle th, SamplerParams params) noexcept; // conceptually bind the set to the command buffer void bind( - OpenGLContext& gl, + OpenGLState& gl, HandleAllocatorGL& handleAllocator, OpenGLProgram const& p, descriptor_set_t set, uint32_t const* offsets, bool offsetsOnly) const noexcept; @@ -138,7 +138,7 @@ private: }; static_assert(sizeof(Descriptor) <= 32); - static void updateTextureView(OpenGLContext& gl, + static void updateTextureView(OpenGLState& gl, HandleAllocatorGL& handleAllocator, GLuint unit, GLTexture const* t) noexcept; utils::FixedCapacityVector descriptors; // 16 diff --git a/filament/backend/src/opengl/GLMemoryMappedBuffer.cpp b/filament/backend/src/opengl/GLMemoryMappedBuffer.cpp index fb940fc00f..3c778f8853 100644 --- a/filament/backend/src/opengl/GLMemoryMappedBuffer.cpp +++ b/filament/backend/src/opengl/GLMemoryMappedBuffer.cpp @@ -18,8 +18,8 @@ #include "GLBufferObject.h" #include "GLUtils.h" -#include "OpenGLContext.h" #include "OpenGLDriver.h" +#include "OpenGLState.h" #include "gl_headers.h" @@ -45,7 +45,7 @@ GLMemoryMappedBuffer::GLMemoryMappedBuffer() = default; GLMemoryMappedBuffer::~GLMemoryMappedBuffer() = default; -GLMemoryMappedBuffer::GLMemoryMappedBuffer(OpenGLContext& glc, HandleAllocatorGL& handleAllocator, +GLMemoryMappedBuffer::GLMemoryMappedBuffer(OpenGLState& gls, HandleAllocatorGL& handleAllocator, BufferObjectHandle boh, size_t const offset, size_t const size, MapBufferAccessFlags const access) : boh(boh), access(access) { @@ -60,7 +60,7 @@ GLMemoryMappedBuffer::GLMemoryMappedBuffer(OpenGLContext& glc, HandleAllocatorGL assert_invariant(any(bo->usage & BufferUsage::SHARED_WRITE_BIT)); } - if (UTILS_UNLIKELY(glc.isES2())) { + if (UTILS_UNLIKELY(gls.isES2())) { gl.vaddr = static_cast(bo->gl.buffer) + offset; gl.size = size; gl.offset = offset; @@ -85,7 +85,7 @@ GLMemoryMappedBuffer::GLMemoryMappedBuffer(OpenGLContext& glc, HandleAllocatorGL gl_access |= GL_MAP_INVALIDATE_RANGE_BIT; } - glc.bindBuffer(bo->gl.binding, bo->gl.id); + gls.bindBuffer(bo->gl.binding, bo->gl.id); addr = glMapBufferRange(bo->gl.binding, GLsizeiptr(offset), GLsizeiptr(size), gl_access); @@ -103,20 +103,20 @@ GLMemoryMappedBuffer::GLMemoryMappedBuffer(OpenGLContext& glc, HandleAllocatorGL #endif } -void GLMemoryMappedBuffer::unmap(OpenGLContext& glc, HandleAllocatorGL& handleAllocator) const { +void GLMemoryMappedBuffer::unmap(OpenGLState& gls, HandleAllocatorGL& handleAllocator) const { GLBufferObject* const bo = handleAllocator.handle_cast(boh); assert_invariant(bo); assert_invariant(bo->mappingCount > 0); bo->mappingCount--; - if (UTILS_UNLIKELY(glc.isES2())) { + if (UTILS_UNLIKELY(gls.isES2())) { // nothing to do return; } #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - glc.bindBuffer(gl.binding, gl.id); + gls.bindBuffer(gl.binding, gl.id); # if !defined(__EMSCRIPTEN__) // don't unmap if we don't have a mapping or it didn't work if (UTILS_LIKELY(gl.vaddr)) { @@ -131,7 +131,7 @@ void GLMemoryMappedBuffer::unmap(OpenGLContext& glc, HandleAllocatorGL& handleAl #endif } -void GLMemoryMappedBuffer::copy(OpenGLContext& glc, OpenGLDriver& gld, +void GLMemoryMappedBuffer::copy(OpenGLState& gls, OpenGLDriver& gld, size_t const offset, BufferDescriptor&& data) const { assert_invariant(any(access & MapBufferAccessFlags::WRITE_BIT)); assert_invariant(offset + data.size <= gl.size); @@ -139,11 +139,11 @@ void GLMemoryMappedBuffer::copy(OpenGLContext& glc, OpenGLDriver& gld, if (UTILS_LIKELY(gl.vaddr)) { memcpy(static_cast(gl.vaddr) + offset, data.buffer, data.size); } else { - assert_invariant(!glc.isES2()); + assert_invariant(!gls.isES2()); // we couldn't map (WebGL or error), revert to glBufferSubData. In the future we can // improve this by keeping around the BufferDescriptor and coalescing the calls // to glBufferSubData. - glc.bindBuffer(gl.binding, gl.id); + gls.bindBuffer(gl.binding, gl.id); glBufferSubData(gl.binding, GLintptr(gl.offset + offset), GLsizeiptr(data.size), data.buffer); CHECK_GL_ERROR(); } diff --git a/filament/backend/src/opengl/GLMemoryMappedBuffer.h b/filament/backend/src/opengl/GLMemoryMappedBuffer.h index 8b2640bef5..978e87dccc 100644 --- a/filament/backend/src/opengl/GLMemoryMappedBuffer.h +++ b/filament/backend/src/opengl/GLMemoryMappedBuffer.h @@ -31,7 +31,7 @@ namespace filament::backend { -class OpenGLContext; +class OpenGLState; class OpenGLDriver; class BufferDescriptor; @@ -50,14 +50,14 @@ struct GLMemoryMappedBuffer : public HwMemoryMappedBuffer { GLMemoryMappedBuffer(); - GLMemoryMappedBuffer(OpenGLContext& glc, HandleAllocatorGL& handleAllocator, + GLMemoryMappedBuffer(OpenGLState& gls, HandleAllocatorGL& handleAllocator, BufferObjectHandle boh, size_t offset, size_t size, MapBufferAccessFlags access); ~GLMemoryMappedBuffer(); - void unmap(OpenGLContext& gl, HandleAllocatorGL& handleAllocator) const; + void unmap(OpenGLState& gls, HandleAllocatorGL& handleAllocator) const; - void copy(OpenGLContext& glc, OpenGLDriver& gld, + void copy(OpenGLState& gls, OpenGLDriver& gld, size_t offset, BufferDescriptor&& data) const; }; diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index 3b69284cc3..ded2ce9ec7 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -15,6 +15,7 @@ */ #include "OpenGLContext.h" +#include "OpenGLState.h" #include "GLUtils.h" #include "OpenGLTimerQuery.h" @@ -29,9 +30,7 @@ #include #include -#include #include -#include #include #include @@ -68,40 +67,37 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept { 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); + vendor = (char const*)glGetString(GL_VENDOR); + renderer = (char const*)glGetString(GL_RENDERER); + version = (char const*)glGetString(GL_VERSION); + shader = (char const*)glGetString(GL_SHADING_LANGUAGE_VERSION); - LOG(INFO) << "[" << state.vendor << "], [" << state.renderer << "], " - "[" << state.version << "], [" << state.shader << "]"; + LOG(INFO) << "[" << vendor << "], [" << renderer << "], " + "[" << version << "], [" << shader << "]"; /* * Figure out GL / GLES version, extensions and capabilities we need to * determine the feature level */ - queryOpenGLVersion(&state.major, &state.minor); + queryOpenGLVersion(&major, &minor); #if defined(BACKEND_OPENGL_VERSION_GLES) if (UTILS_UNLIKELY(driverConfig.forceGLES2Context)) { - state.major = 2; - state.minor = 0; + major = 2; + minor = 0; } #endif - initExtensions(&ext, state.major, state.minor); + initExtensions(&ext, major, minor); - initProcs(&procs, ext, state.major, state.minor); + initProcs(&procs, ext, major, minor); - initBugs(&bugs, ext, state.major, state.minor, - state.vendor, state.renderer, state.version, state.shader); + initBugs(&bugs, ext, major, minor, + vendor, renderer, version, shader); initWorkarounds(bugs, &ext); @@ -113,7 +109,7 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform, glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &gets.max_3d_texture_size); glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &gets.max_array_texture_layers); - mFeatureLevel = resolveFeatureLevel(state.major, state.minor, ext, gets, bugs); + mFeatureLevel = resolveFeatureLevel(major, minor, ext, gets, bugs); #ifdef BACKEND_OPENGL_VERSION_GLES mShaderModel = ShaderModel::MOBILE; @@ -201,8 +197,6 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform, 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) { @@ -263,120 +257,20 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform, 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; +OpenGLContext::~OpenGLContext() noexcept = default; + +OpenGLState* OpenGLContext::createState() noexcept { + auto* state = new(std::nothrow) OpenGLState(*this); + assert_invariant(state); + return state; } -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 const& closure) { - 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) { - - // 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::destroyState(OpenGLState* state) noexcept { + if (state) { + state->terminate(); + delete state; } } @@ -870,413 +764,4 @@ void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor) #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()); - } - // by construction, defaultFboForThisContext has a value. value_or() avoids a throwing call. - buffer = defaultFboForThisContext.value_or(0); - } - 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::deleteBuffer(GLuint buffer, GLenum target) noexcept { - glDeleteBuffers(1, &buffer); - - // bindings of bound buffers are reset to 0 - size_t const targetIndex = getIndexForBufferTarget(target); - auto& genericBinding = state.buffers.genericBinding[targetIndex]; - if (genericBinding == buffer) { - genericBinding = 0; - } - - if (UTILS_UNLIKELY(bugs.rebind_buffer_after_deletion)) { - if (genericBinding) { - glBindBuffer(target, genericBinding); - } - } - -#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& indexedBinding = state.buffers.targets[targetIndex]; - UTILS_NOUNROLL - for (auto& entry: indexedBinding.buffers) { - if (entry.name == buffer) { - entry.name = 0; - entry.offset = 0; - entry.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() - 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 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 +} // namespace filament::backend diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h index f446018a4f..391ec2c764 100644 --- a/filament/backend/src/opengl/OpenGLContext.h +++ b/filament/backend/src/opengl/OpenGLContext.h @@ -17,9 +17,6 @@ #ifndef TNT_FILAMENT_BACKEND_OPENGLCONTEXT_H #define TNT_FILAMENT_BACKEND_OPENGLCONTEXT_H - -#include "OpenGLTimerQuery.h" - #include #include @@ -34,13 +31,8 @@ #include #include -#include - #include -#include -#include #include -#include #include #include @@ -48,8 +40,9 @@ namespace filament::backend { class OpenGLPlatform; +class OpenGLState; -class OpenGLContext final : public TimerQueryFactoryInterface { +class OpenGLContext final { public: static constexpr const size_t MAX_TEXTURE_UNIT_COUNT = MAX_SAMPLER_COUNT; static constexpr const size_t DUMMY_TEXTURE_BINDING = 7; // highest binding guaranteed to work with ES2 @@ -77,12 +70,12 @@ public: // be updated (see OpenGLDriver::updateVertexArrayObject()) uint8_t vertexBufferVersion = 0; // 1 - // if this differs from OpenGLContext::state.age, this VAO needs to + // if this differs from OpenGLState::state.age, this VAO needs to // be updated (see OpenGLDriver::updateVertexArrayObject()) uint8_t stateVersion = 0; // 1 - // If this differs from OpenGLContext::state.age, this VAO's name needs to be updated. - // See OpenGLContext::bindVertexArray() + // If this differs from OpenGLState::state.age, this VAO's name needs to be updated. + // See OpenGLState::bindVertexArray() uint8_t nameVersion = 0; // 1 // Size in bytes of indices in the index buffer (1 or 2) @@ -98,24 +91,18 @@ public: explicit OpenGLContext(OpenGLPlatform& platform, Platform::DriverConfig const& driverConfig) noexcept; - ~OpenGLContext() noexcept final; + ~OpenGLContext() noexcept; - void terminate() noexcept; - - // TimerQueryInterface ------------------------------------------------------------------------ - - // note: OpenGLContext being final ensures (clang) these are not called through the vtable - void createTimerQuery(GLTimerQuery* query) override; - void destroyTimerQuery(GLTimerQuery* query) override; - void beginTimeElapsedQuery(GLTimerQuery* query) override; - void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) override; + // State lifecycle - allocate/deallocate per-thread OpenGLState instances + OpenGLState* createState() noexcept; + void destroyState(OpenGLState* state) noexcept; // -------------------------------------------------------------------------------------------- template inline bool isAtLeastGL() const noexcept { #ifdef BACKEND_OPENGL_VERSION_GL - return state.major > MAJOR || (state.major == MAJOR && state.minor >= MINOR); + return major > MAJOR || (major == MAJOR && minor >= MINOR); #else return false; #endif @@ -124,7 +111,7 @@ public: template inline bool isAtLeastGLES() const noexcept { #ifdef BACKEND_OPENGL_VERSION_GLES - return state.major > MAJOR || (state.major == MAJOR && state.minor >= MINOR); + return major > MAJOR || (major == MAJOR && minor >= MINOR); #else return false; #endif @@ -154,60 +141,13 @@ public: #endif } - constexpr inline size_t getIndexForCap(GLenum cap) noexcept; - constexpr static inline size_t getIndexForBufferTarget(GLenum target) noexcept; - ShaderModel getShaderModel() const noexcept { return mShaderModel; } + FeatureLevel getFeatureLevel() const noexcept { return mFeatureLevel; } - void resetState() noexcept; - + OpenGLPlatform& getPlatform() const noexcept { return mPlatform; } + Platform::DriverConfig const& getDriverConfig() const noexcept { return mDriverConfig; } bool isGpuTimeSupported() const noexcept { return mGpuTimeSupported; } - inline void useProgram(GLuint program) noexcept; - - void pixelStore(GLenum, GLint) noexcept; - inline void activeTexture(GLuint unit) noexcept; - inline void bindTexture(GLuint unit, GLuint target, GLuint texId, bool external) noexcept; - - void unbindTexture(GLenum target, GLuint id) noexcept; - void unbindTextureUnit(GLuint unit) noexcept; - inline void bindVertexArray(RenderPrimitive const* p) noexcept; - inline void bindSampler(GLuint unit, GLuint sampler) noexcept; - void unbindSampler(GLuint sampler) noexcept; - - void bindBuffer(GLenum target, GLuint buffer) noexcept; - inline void bindBufferRange(GLenum target, GLuint index, GLuint buffer, - GLintptr offset, GLsizeiptr size) noexcept; - - GLuint bindFramebuffer(GLenum target, GLuint buffer) noexcept; - void unbindFramebuffer(GLenum target) noexcept; - - inline void enableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept; - inline void disableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept; - inline void enable(GLenum cap) noexcept; - inline void disable(GLenum cap) noexcept; - inline void frontFace(GLenum mode) noexcept; - inline void cullFace(GLenum mode) noexcept; - inline void blendEquation(GLenum modeRGB, GLenum modeA) noexcept; - inline void blendFunction(GLenum srcRGB, GLenum srcA, GLenum dstRGB, GLenum dstA) noexcept; - inline void colorMask(GLboolean flag) noexcept; - inline void depthMask(GLboolean flag) noexcept; - inline void depthFunc(GLenum func) noexcept; - inline void stencilFuncSeparate(GLenum funcFront, GLint refFront, GLuint maskFront, - GLenum funcBack, GLint refBack, GLuint maskBack) noexcept; - inline void stencilOpSeparate(GLenum sfailFront, GLenum dpfailFront, GLenum dppassFront, - GLenum sfailBack, GLenum dpfailBack, GLenum dppassBack) noexcept; - inline void stencilMaskSeparate(GLuint maskFront, GLuint maskBack) noexcept; - inline void polygonOffset(GLfloat factor, GLfloat units) noexcept; - - inline void setScissor(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept; - inline void viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept; - inline void depthRange(GLclampf near, GLclampf far) noexcept; - - void deleteBuffer(GLuint buffer, GLenum target) noexcept; - void deleteVertexArray(GLuint vao) noexcept; - - void destroyWithContext(size_t index, std::function const& closure); // glGet*() values struct Gets { @@ -357,143 +297,6 @@ public: } bugs = {}; - // state getters -- as needed. - vec4gli const& getViewport() const { return state.window.viewport; } - - // function to handle state changes we don't control - void updateTexImage(GLenum target, GLuint id) noexcept { - assert_invariant(target == GL_TEXTURE_EXTERNAL_OES); - // if another target is bound to this texture unit, unbind that texture - if (UTILS_UNLIKELY(state.textures.units[state.textures.active].target != target)) { - glBindTexture(state.textures.units[state.textures.active].target, 0); - state.textures.units[state.textures.active].target = GL_TEXTURE_EXTERNAL_OES; - } - // the texture is already bound to `target`, we just update our internal state - state.textures.units[state.textures.active].id = id; - } - void resetProgram() noexcept { state.program.use = 0; } - - FeatureLevel getFeatureLevel() const noexcept { return mFeatureLevel; } - - // This is the index of the context in use. Must be either 0 (Unprotected) or 1 (Protected). - // This is used to manage the OpenGL name of ContainerObjects within each context. - uint32_t contextIndex = 0; - - // Try to keep the State structure sorted by data-access patterns - struct State { - State() noexcept = default; - // make sure we don't copy this state by accident - State(State const& rhs) = delete; - State(State&& rhs) noexcept = delete; - State& operator=(State const& rhs) = delete; - State& operator=(State&& rhs) noexcept = delete; - - GLint major = 0; - GLint minor = 0; - - char const* vendor = nullptr; - char const* renderer = nullptr; - char const* version = nullptr; - char const* shader = nullptr; - - GLuint draw_fbo = 0; - GLuint read_fbo = 0; - - struct { - GLuint use = 0; - } program; - - struct { - RenderPrimitive* p = nullptr; - } vao; - - struct { - GLenum frontFace = GL_CCW; - GLenum cullFace = GL_BACK; - GLenum blendEquationRGB = GL_FUNC_ADD; - GLenum blendEquationA = GL_FUNC_ADD; - GLenum blendFunctionSrcRGB = GL_ONE; - GLenum blendFunctionSrcA = GL_ONE; - GLenum blendFunctionDstRGB = GL_ZERO; - GLenum blendFunctionDstA = GL_ZERO; - GLboolean colorMask = GL_TRUE; - GLboolean depthMask = GL_TRUE; - GLenum depthFunc = GL_LESS; - } raster; - - struct { - struct StencilFunc { - GLenum func = GL_ALWAYS; - GLint ref = 0; - GLuint mask = ~GLuint(0); - bool operator != (StencilFunc const& rhs) const noexcept { - return func != rhs.func || ref != rhs.ref || mask != rhs.mask; - } - }; - struct StencilOp { - GLenum sfail = GL_KEEP; - GLenum dpfail = GL_KEEP; - GLenum dppass = GL_KEEP; - bool operator != (StencilOp const& rhs) const noexcept { - return sfail != rhs.sfail || dpfail != rhs.dpfail || dppass != rhs.dppass; - } - }; - struct { - StencilFunc func; - StencilOp op; - GLuint stencilMask = ~GLuint(0); - } front, back; - } stencil; - - struct PolygonOffset { - GLfloat factor = 0; - GLfloat units = 0; - bool operator != (PolygonOffset const& rhs) const noexcept { - return factor != rhs.factor || units != rhs.units; - } - } polygonOffset; - - struct { - utils::bitset32 caps; - } enables; - - struct { - struct { - struct { - GLuint name = 0; - GLintptr offset = 0; - GLsizeiptr size = 0; - } buffers[MAX_BUFFER_BINDINGS]; - } targets[3]; // there are only 3 indexed buffer targets - GLuint genericBinding[7] = {}; - } buffers; - - struct { - GLuint active = 0; // zero-based - struct { - GLuint sampler = 0; - GLuint target = 0; - GLuint id = 0; - } units[MAX_TEXTURE_UNIT_COUNT]; - } textures; - - struct { - GLint row_length = 0; - GLint alignment = 4; - } unpack; - - struct { - GLint alignment = 4; - } pack; - - struct { - vec4gli scissor { 0 }; - vec4gli viewport { 0 }; - vec2glf depthRange { 0.0f, 1.0f }; - } window; - uint8_t age = 0; - } state; - struct Procs { void (* bindVertexArray)(GLuint array); void (* deleteVertexArrays)(GLsizei n, const GLuint* arrays); @@ -511,43 +314,25 @@ public: void (* maxShaderCompilerThreadsKHR)(GLuint count); } procs{}; - void unbindEverything() noexcept; - void synchronizeStateAndCache(size_t index); - -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - GLuint getSamplerSlow(SamplerParams sp) const noexcept; - - inline GLuint getSampler(SamplerParams sp) const noexcept { - assert_invariant(!sp.padding0); - assert_invariant(!sp.padding1); - assert_invariant(!sp.padding2); - auto& samplerMap = mSamplerMap; - auto pos = samplerMap.find(sp); - if (UTILS_UNLIKELY(pos == samplerMap.end())) { - return getSamplerSlow(sp); - } - return pos->second; - } -#endif + // GL version info — immutable after construction + GLint major = 0; + GLint minor = 0; + // GL info strings — immutable after construction + char const* vendor = nullptr; + char const* renderer = nullptr; + char const* version = nullptr; + char const* shader = nullptr; private: OpenGLPlatform& mPlatform; ShaderModel mShaderModel = ShaderModel::MOBILE; FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1; - TimerQueryFactoryInterface* mTimerQueryFactory = nullptr; - std::vector> mDestroyWithNormalContext; - RenderPrimitive mDefaultVAO; - std::optional mDefaultFbo[2]; // 0:Unprotected, 1:Protected - mutable tsl::robin_map mSamplerMap; Platform::DriverConfig const mDriverConfig; bool mGpuTimeSupported = false; - void bindFramebufferResolved(GLenum target, GLuint buffer) noexcept; - const std::array, sizeof(bugs)> mBugDatabase{{ { bugs.disable_glFlush, "disable_glFlush", @@ -639,315 +424,8 @@ private: Extensions const& exts, Gets const& gets, Bugs const& bugs) noexcept; - - template - static inline void update_state(T& state, T const& expected, F functor, bool force = false) noexcept { - if (UTILS_UNLIKELY(force || state != expected)) { - state = expected; - functor(); - } - } - - void setDefaultState() noexcept; }; -// ------------------------------------------------------------------------------------------------ - -constexpr size_t OpenGLContext::getIndexForCap(GLenum cap) noexcept { //NOLINT - size_t index = 0; - switch (cap) { - case GL_BLEND: index = 0; break; - case GL_CULL_FACE: index = 1; break; - case GL_SCISSOR_TEST: index = 2; break; - case GL_DEPTH_TEST: index = 3; break; - case GL_STENCIL_TEST: index = 4; break; - case GL_DITHER: index = 5; break; - case GL_SAMPLE_ALPHA_TO_COVERAGE: index = 6; break; - case GL_SAMPLE_COVERAGE: index = 7; break; - case GL_POLYGON_OFFSET_FILL: index = 8; break; -#ifdef GL_ARB_seamless_cube_map - case GL_TEXTURE_CUBE_MAP_SEAMLESS: index = 9; break; -#endif -#ifdef BACKEND_OPENGL_VERSION_GL - case GL_PROGRAM_POINT_SIZE: index = 10; break; -#endif - case GL_DEPTH_CLAMP: index = 11; break; - default: break; - } - assert_invariant(index < state.enables.caps.size()); - return index; -} - -constexpr size_t OpenGLContext::getIndexForBufferTarget(GLenum target) noexcept { - size_t index = 0; - switch (target) { - // The indexed buffers MUST be first in this list (those usable with bindBufferRange) -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - case GL_UNIFORM_BUFFER: index = 0; break; - case GL_TRANSFORM_FEEDBACK_BUFFER: index = 1; break; -#if defined(BACKEND_OPENGL_LEVEL_GLES31) - case GL_SHADER_STORAGE_BUFFER: index = 2; break; -#endif -#endif - case GL_ARRAY_BUFFER: index = 3; break; - case GL_ELEMENT_ARRAY_BUFFER: index = 4; break; -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - case GL_PIXEL_PACK_BUFFER: index = 5; break; - case GL_PIXEL_UNPACK_BUFFER: index = 6; break; -#endif - default: break; - } - assert_invariant(index < sizeof(state.buffers.genericBinding)/sizeof(state.buffers.genericBinding[0])); // NOLINT(misc-redundant-expression) - return index; -} - -// ------------------------------------------------------------------------------------------------ - -void OpenGLContext::activeTexture(GLuint unit) noexcept { - assert_invariant(unit < MAX_TEXTURE_UNIT_COUNT); - update_state(state.textures.active, unit, [&]() { - glActiveTexture(GL_TEXTURE0 + unit); - }); -} - -void OpenGLContext::bindSampler(GLuint unit, GLuint sampler) noexcept { - assert_invariant(unit < MAX_TEXTURE_UNIT_COUNT); - assert_invariant(mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1); -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - update_state(state.textures.units[unit].sampler, sampler, [&]() { - glBindSampler(unit, sampler); - }); -#endif -} - -void OpenGLContext::setScissor(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept { - vec4gli const scissor(left, bottom, width, height); - update_state(state.window.scissor, scissor, [&]() { - glScissor(left, bottom, width, height); - }); -} - -void OpenGLContext::viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept { - vec4gli const viewport(left, bottom, width, height); - update_state(state.window.viewport, viewport, [&]() { - glViewport(left, bottom, width, height); - }); -} - -void OpenGLContext::depthRange(GLclampf near, GLclampf far) noexcept { - vec2glf const depthRange(near, far); - update_state(state.window.depthRange, depthRange, [&]() { - glDepthRangef(near, far); - }); -} - -void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept { - RenderPrimitive* vao = p ? const_cast(p) : &mDefaultVAO; - update_state(state.vao.p, vao, [&]() { - - // See if we need to create a name for this VAO on the fly, this would happen if: - // - we're not the default VAO, because its name is always 0 - // - our name is 0, this could happen if this VAO was created in the "other" context - // - the nameVersion is out of date *and* we're on the protected context, in this case: - // - the name must be stale from a previous use of this context because we always - // destroy the protected context when we're done with it. - bool const recreateVaoName = vao != &mDefaultVAO && - ((vao->vao[contextIndex] == 0) || - (vao->nameVersion != state.age && contextIndex == 1)); - if (UTILS_UNLIKELY(recreateVaoName)) { - vao->nameVersion = state.age; - procs.genVertexArrays(1, &vao->vao[contextIndex]); - } - - procs.bindVertexArray(vao->vao[contextIndex]); - // update GL_ELEMENT_ARRAY_BUFFER, which is updated by glBindVertexArray - size_t const targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER); - state.buffers.genericBinding[targetIndex] = vao->elementArray; - if (UTILS_UNLIKELY(bugs.vao_doesnt_store_element_array_buffer_binding || recreateVaoName)) { - // This shouldn't be needed, but it looks like some drivers don't do the implicit - // glBindBuffer(). - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao->elementArray); - } - }); -} - -void OpenGLContext::bindBufferRange(GLenum target, GLuint index, GLuint buffer, - GLintptr offset, GLsizeiptr size) noexcept { - assert_invariant(mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1); - -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 -# ifdef BACKEND_OPENGL_LEVEL_GLES31 - assert_invariant(false - || target == GL_UNIFORM_BUFFER - || target == GL_TRANSFORM_FEEDBACK_BUFFER - || target == GL_SHADER_STORAGE_BUFFER); -# else - assert_invariant(false - || target == GL_UNIFORM_BUFFER - || target == GL_TRANSFORM_FEEDBACK_BUFFER); -# endif - size_t const targetIndex = getIndexForBufferTarget(target); - // this ALSO sets the generic binding - assert_invariant(targetIndex < sizeof(state.buffers.targets) / sizeof(*state.buffers.targets)); - if ( state.buffers.targets[targetIndex].buffers[index].name != buffer - || state.buffers.targets[targetIndex].buffers[index].offset != offset - || state.buffers.targets[targetIndex].buffers[index].size != size) { - state.buffers.targets[targetIndex].buffers[index].name = buffer; - state.buffers.targets[targetIndex].buffers[index].offset = offset; - state.buffers.targets[targetIndex].buffers[index].size = size; - state.buffers.genericBinding[targetIndex] = buffer; - glBindBufferRange(target, index, buffer, offset, size); - } -#endif -} - -void OpenGLContext::bindTexture(GLuint unit, GLuint target, GLuint texId, bool external) noexcept { - // another texture is bound to the same unit with a different target, - // unbind the texture from the current target - update_state(state.textures.units[unit].target, target, [&]() { - activeTexture(unit); - glBindTexture(state.textures.units[unit].target, 0); - }); - update_state(state.textures.units[unit].id, texId, [&]() { - activeTexture(unit); - glBindTexture(target, texId); - }, external); -} - -void OpenGLContext::useProgram(GLuint program) noexcept { - update_state(state.program.use, program, [&]() { - glUseProgram(program); - }); -} - -void OpenGLContext::enableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept { - assert_invariant(rp); - assert_invariant(index < rp->vertexAttribArray.size()); - bool const force = rp->stateVersion != state.age; - if (UTILS_UNLIKELY(force || !rp->vertexAttribArray[index])) { - rp->vertexAttribArray.set(index); - glEnableVertexAttribArray(index); - } -} - -void OpenGLContext::disableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept { - assert_invariant(rp); - assert_invariant(index < rp->vertexAttribArray.size()); - bool const force = rp->stateVersion != state.age; - if (UTILS_UNLIKELY(force || rp->vertexAttribArray[index])) { - rp->vertexAttribArray.unset(index); - glDisableVertexAttribArray(index); - } -} - -void OpenGLContext::enable(GLenum cap) noexcept { - size_t const index = getIndexForCap(cap); - if (UTILS_UNLIKELY(!state.enables.caps[index])) { - state.enables.caps.set(index); - glEnable(cap); - } -} - -void OpenGLContext::disable(GLenum cap) noexcept { - size_t const index = getIndexForCap(cap); - if (UTILS_UNLIKELY(state.enables.caps[index])) { - state.enables.caps.unset(index); - glDisable(cap); - } -} - -void OpenGLContext::frontFace(GLenum mode) noexcept { - update_state(state.raster.frontFace, mode, [&]() { - glFrontFace(mode); - }); -} - -void OpenGLContext::cullFace(GLenum mode) noexcept { - update_state(state.raster.cullFace, mode, [&]() { - glCullFace(mode); - }); -} - -void OpenGLContext::blendEquation(GLenum modeRGB, GLenum modeA) noexcept { - if (UTILS_UNLIKELY( - state.raster.blendEquationRGB != modeRGB || state.raster.blendEquationA != modeA)) { - state.raster.blendEquationRGB = modeRGB; - state.raster.blendEquationA = modeA; - glBlendEquationSeparate(modeRGB, modeA); - } -} - -void OpenGLContext::blendFunction(GLenum srcRGB, GLenum srcA, GLenum dstRGB, GLenum dstA) noexcept { - if (UTILS_UNLIKELY( - state.raster.blendFunctionSrcRGB != srcRGB || - state.raster.blendFunctionSrcA != srcA || - state.raster.blendFunctionDstRGB != dstRGB || - state.raster.blendFunctionDstA != dstA)) { - state.raster.blendFunctionSrcRGB = srcRGB; - state.raster.blendFunctionSrcA = srcA; - state.raster.blendFunctionDstRGB = dstRGB; - state.raster.blendFunctionDstA = dstA; - glBlendFuncSeparate(srcRGB, dstRGB, srcA, dstA); - } -} - -void OpenGLContext::colorMask(GLboolean flag) noexcept { - update_state(state.raster.colorMask, flag, [&]() { - glColorMask(flag, flag, flag, flag); - }); -} -void OpenGLContext::depthMask(GLboolean flag) noexcept { - update_state(state.raster.depthMask, flag, [&]() { - glDepthMask(flag); - }); -} - -void OpenGLContext::depthFunc(GLenum func) noexcept { - update_state(state.raster.depthFunc, func, [&]() { - glDepthFunc(func); - }); -} - -void OpenGLContext::stencilFuncSeparate(GLenum funcFront, GLint refFront, GLuint maskFront, - GLenum funcBack, GLint refBack, GLuint maskBack) noexcept { - update_state(state.stencil.front.func, {funcFront, refFront, maskFront}, [&]() { - glStencilFuncSeparate(GL_FRONT, funcFront, refFront, maskFront); - }); - update_state(state.stencil.back.func, {funcBack, refBack, maskBack}, [&]() { - glStencilFuncSeparate(GL_BACK, funcBack, refBack, maskBack); - }); -} - -void OpenGLContext::stencilOpSeparate(GLenum sfailFront, GLenum dpfailFront, GLenum dppassFront, - GLenum sfailBack, GLenum dpfailBack, GLenum dppassBack) noexcept { - update_state(state.stencil.front.op, {sfailFront, dpfailFront, dppassFront}, [&]() { - glStencilOpSeparate(GL_FRONT, sfailFront, dpfailFront, dppassFront); - }); - update_state(state.stencil.back.op, {sfailBack, dpfailBack, dppassBack}, [&]() { - glStencilOpSeparate(GL_BACK, sfailBack, dpfailBack, dppassBack); - }); -} - -void OpenGLContext::stencilMaskSeparate(GLuint maskFront, GLuint maskBack) noexcept { - update_state(state.stencil.front.stencilMask, maskFront, [&]() { - glStencilMaskSeparate(GL_FRONT, maskFront); - }); - update_state(state.stencil.back.stencilMask, maskBack, [&]() { - glStencilMaskSeparate(GL_BACK, maskBack); - }); -} - -void OpenGLContext::polygonOffset(GLfloat factor, GLfloat units) noexcept { - update_state(state.polygonOffset, { factor, units }, [&]() { - if (factor != 0 || units != 0) { - glPolygonOffset(factor, units); - enable(GL_POLYGON_OFFSET_FILL); - } else { - disable(GL_POLYGON_OFFSET_FILL); - } - }); -} - -} // namespace filament +} // namespace filament::backend #endif //TNT_FILAMENT_BACKEND_OPENGLCONTEXT_H diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index df79e2efab..7fec3bff1b 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -23,6 +23,7 @@ #include "OpenGLContext.h" #include "OpenGLDriverFactory.h" #include "OpenGLProgram.h" +#include "OpenGLState.h" #include "OpenGLTimerQuery.h" #include "SystraceProfile.h" #include "gl_headers.h" @@ -327,14 +328,26 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi ThreadWorker::Config threadWorkerConfig{ "JobQueueThreadWorker", JobSystem::Priority::NORMAL, - [this]() { mPlatform.createContext(true); }, - [this]() { mPlatform.releaseContext(); }, + [this]() { + mPlatform.createContext(true); + mWorkerState = mContext.createState(); + assert_invariant(mWorkerState); + }, + [this]() { + mContext.destroyState(mWorkerState); + mWorkerState = nullptr; + mPlatform.releaseContext(); + }, }; mJobWorker = ThreadWorker::create(mJobQueue, std::move(threadWorkerConfig)); } else { mJobWorker = AmortizationWorker::create(mJobQueue); } } + + // Create the backend thread's state after mContext is fully initialized + mBackendState = mContext.createState(); + assert_invariant(mBackendState); } OpenGLDriver::~OpenGLDriver() noexcept { // NOLINT(modernize-use-equals-default) @@ -394,7 +407,8 @@ void OpenGLDriver::terminate() { stopServiceThread(); } - mContext.terminate(); + mContext.destroyState(mBackendState); + mBackendState = nullptr; mPlatform.terminate(); } @@ -411,11 +425,11 @@ utils::FixedCapacityVector OpenGLDriver::getShaderLanguages( // Change and track GL state // ------------------------------------------------------------------------------------------------ void OpenGLDriver::resetState(int) { - mContext.resetState(); + getBackendState().syncState(); } void OpenGLDriver::bindSampler(GLuint const unit, GLuint const sampler) noexcept { - mContext.bindSampler(unit, sampler); + getBackendState().bindSampler(unit, sampler); } void OpenGLDriver::setPushConstant(ShaderStage const stage, uint8_t const index, @@ -461,7 +475,7 @@ void OpenGLDriver::setPushConstant(ShaderStage const stage, uint8_t const index, void OpenGLDriver::bindTexture(GLuint const unit, GLTexture const* t) noexcept { assert_invariant(t != nullptr); - mContext.bindTexture(unit, t->gl.target, t->gl.id, t->gl.external); + getBackendState().bindTexture(unit, t->gl.target, t->gl.id, t->gl.external); } bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept { @@ -469,7 +483,7 @@ bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept { if (mBoundProgram != p) { // compile/link the program if needed and call glUseProgram. // This call may block until the program linking process is complete. - success = p->use(this, mContext); + success = p->use(this, getBackendState()); assert_invariant(success == p->isValid()); if (success) { // TODO: we could even improve this if the program could tell us which of the descriptors @@ -494,7 +508,7 @@ bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept { void OpenGLDriver::setRasterState(RasterState const rs) noexcept { - auto& gl = mContext; + auto& gl = getBackendState(); mRenderPassColorWrite |= rs.colorWrite; mRenderPassDepthWrite |= rs.depthWrite; @@ -554,7 +568,7 @@ void OpenGLDriver::setRasterState(RasterState const rs) noexcept { } void OpenGLDriver::setStencilState(StencilState const ss) noexcept { - auto& gl = mContext; + auto& gl = getBackendState(); mRenderPassStencilWrite |= ss.stencilWrite; @@ -762,9 +776,8 @@ void OpenGLDriver::createVertexBufferR( mHandleAllocator.associateTagToHandle(vbh.getId(), std::move(tag)); } -void OpenGLDriver::createIndexBufferCommon(Handle ibh, ElementType const elementType, +void OpenGLDriver::createIndexBufferCommon(OpenGLState& gl, Handle ibh, ElementType const elementType, uint32_t indexCount, BufferUsage const usage, utils::ImmutableCString&& tag) { - auto& gl = mContext; uint8_t const elementSize = static_cast(getElementTypeSize(elementType)); GLIndexBuffer* ib = handle_cast(ibh); glGenBuffers(1, &ib->gl.buffer); @@ -790,7 +803,7 @@ void OpenGLDriver::createIndexBufferR( // subsequent backend APIs can handle operations based on this setting. construct(ibh, elementSize, indexCount, false); - createIndexBufferCommon(ibh, elementType, indexCount, usage, std::move(tag)); + createIndexBufferCommon(getBackendState(), ibh, elementType, indexCount, usage, std::move(tag)); } void OpenGLDriver::createIndexBufferAsyncR( @@ -811,7 +824,7 @@ void OpenGLDriver::createIndexBufferAsyncR( getJobQueue()->push([this, ibh, elementType, indexCount, usage, handler, callback, user, tag=std::move(tag)]() mutable { DEBUG_MARKER_NAME("createIndexBufferAsyncR") - createIndexBufferCommon(ibh, elementType, indexCount, usage, std::move(tag)); + createIndexBufferCommon(getWorkerState(), ibh, elementType, indexCount, usage, std::move(tag)); // glFlush() should be called when using a shared context for this operation. Without it, // the driver may delay submitting commands to the GPU, preventing other contexts from // seeing the changes immediately. This ensures submitting the current commands right away. @@ -820,11 +833,9 @@ void OpenGLDriver::createIndexBufferAsyncR( }); } -void OpenGLDriver::createBufferObjectCommon(Handle boh, uint32_t byteCount, +void OpenGLDriver::createBufferObjectCommon(OpenGLState& gl, Handle boh, uint32_t byteCount, BufferObjectBinding bindingType, BufferUsage usage, utils::ImmutableCString&& tag) { assert_invariant(byteCount > 0); - - auto& gl = mContext; if (bindingType == BufferObjectBinding::VERTEX) { gl.bindVertexArray(nullptr); } @@ -854,7 +865,7 @@ void OpenGLDriver::createBufferObjectR(Handle boh, uint32_t byte // subsequent backend APIs can handle operations based on this setting. construct(boh, byteCount, bindingType, usage, false); - createBufferObjectCommon(boh, byteCount, bindingType, usage, std::move(tag)); + createBufferObjectCommon(getBackendState(), boh, byteCount, bindingType, usage, std::move(tag)); } void OpenGLDriver::createBufferObjectAsyncR(Handle boh, uint32_t byteCount, @@ -870,7 +881,7 @@ void OpenGLDriver::createBufferObjectAsyncR(Handle boh, uint32_t getJobQueue()->push([this, boh, byteCount, bindingType, usage, handler, callback, user, tag=std::move(tag)]() mutable { DEBUG_MARKER_NAME("createBufferObjectAsyncR") - createBufferObjectCommon(boh, byteCount, bindingType, usage, std::move(tag)); + createBufferObjectCommon(getWorkerState(), boh, byteCount, bindingType, usage, std::move(tag)); // glFlush() should be called when using a shared context for this operation. Without it, // the driver may delay submitting commands to the GPU, preventing other contexts from // seeing the changes immediately. This ensures submitting the current commands right away. @@ -884,7 +895,7 @@ void OpenGLDriver::createRenderPrimitiveR(Handle rph, PrimitiveType const pt, ImmutableCString&& tag) { DEBUG_MARKER() - auto& gl = mContext; + auto& gl = getBackendState(); GLIndexBuffer const* const ib = handle_cast(ibh); assert_invariant(ib->elementSize == 2 || ib->elementSize == 4); @@ -926,12 +937,10 @@ void OpenGLDriver::createProgramR(Handle ph, Program&& program, Immut } UTILS_NOINLINE -void OpenGLDriver::textureStorage(GLTexture* t, +void OpenGLDriver::textureStorage(OpenGLState& gl, GLTexture* t, uint32_t const width, uint32_t const height, uint32_t const depth, bool useProtectedMemory) noexcept { - auto& gl = mContext; - - bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t); + gl.bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t->gl.target, t->gl.id, t->gl.external); gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); #ifdef GL_EXT_protected_textures @@ -999,7 +1008,7 @@ void OpenGLDriver::textureStorage(GLTexture* t, // NOTE: if there is a mix of texture and renderbuffers, "fixed_sample_locations" must be true // NOTE: what's the benefit of setting "fixed_sample_locations" to false? - if (mContext.isAtLeastGL<4, 3>() || mContext.isAtLeastGLES<3, 1>()) { + if (gl.isAtLeastGL<4, 3>() || gl.isAtLeastGLES<3, 1>()) { // only supported from GL 4.3 and GLES 3.1 headers glTexStorage2DMultisample(t->gl.target, t->samples, t->gl.internalFormat, GLsizei(width), GLsizei(height), GL_TRUE); @@ -1026,7 +1035,7 @@ void OpenGLDriver::textureStorage(GLTexture* t, t->depth = depth; } -void OpenGLDriver::createTextureCommon(Handle th, SamplerType target, uint8_t levels, +void OpenGLDriver::createTextureCommon(OpenGLState& gl, Handle th, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, ImmutableCString&& tag) { GLenum internalFormat = getInternalFormat(format); @@ -1047,7 +1056,6 @@ void OpenGLDriver::createTextureCommon(Handle th, SamplerType target, usage |= TextureUsage::SAMPLEABLE; } - auto const& gl = mContext; samples = std::clamp(samples, uint8_t(1u), uint8_t(gl.gets.max_samples)); GLTexture* t = handle_cast(th); @@ -1109,7 +1117,7 @@ void OpenGLDriver::createTextureCommon(Handle th, SamplerType target, #endif } - textureStorage(t, width, height, depth, bool(usage & TextureUsage::PROTECTED)); + textureStorage(gl, t, width, height, depth, bool(usage & TextureUsage::PROTECTED)); } } else { t->gl.internalFormat = internalFormat; @@ -1132,7 +1140,7 @@ void OpenGLDriver::createTextureR(Handle th, SamplerType target, uint // subsequent backend APIs can handle operations based on this setting. construct(th, target, levels, samples, width, height, depth, format, usage, false); - createTextureCommon(th, target, levels, format, samples, width, height, depth, usage, + createTextureCommon(getBackendState(), th, target, levels, format, samples, width, height, depth, usage, std::move(tag)); } @@ -1150,7 +1158,7 @@ void OpenGLDriver::createTextureAsyncR(Handle th, SamplerType target, getJobQueue()->push([this, th, target, levels, format, samples, width, height, depth, usage, handler, callback, user, tag=std::move(tag)]() mutable { DEBUG_MARKER_NAME("createTextureAsyncR") - createTextureCommon(th, target, levels, format, samples, width, height, depth, usage, + createTextureCommon(getWorkerState(), th, target, levels, format, samples, width, height, depth, usage, std::move(tag)); // glFlush() should be called when using a shared context for this operation. Without it, // the driver may delay submitting commands to the GPU, preventing other contexts from @@ -1313,7 +1321,7 @@ void OpenGLDriver::createTextureExternalImage2R(Handle th, SamplerTyp usage |= TextureUsage::SAMPLEABLE; usage &= ~TextureUsage::UPLOADABLE; - auto const& gl = mContext; + auto const& gl = getBackendState(); GLenum internalFormat = getInternalFormat(format); if (UTILS_UNLIKELY(gl.isES2())) { // on ES2, format and internal format must match @@ -1365,7 +1373,7 @@ void OpenGLDriver::createTextureExternalImageR(Handle th, SamplerType usage |= TextureUsage::SAMPLEABLE; usage &= ~TextureUsage::UPLOADABLE; - auto const& gl = mContext; + auto const& gl = getBackendState(); GLenum internalFormat = getInternalFormat(format); if (UTILS_UNLIKELY(gl.isES2())) { // on ES2, format and internal format must match @@ -1414,10 +1422,9 @@ void OpenGLDriver::createTextureExternalImagePlaneR(Handle th, // not relevant for the OpenGL backend } -void OpenGLDriver::importTextureCommon(Handle th, intptr_t const id, +void OpenGLDriver::importTextureCommon(OpenGLState& gl, Handle th, intptr_t const id, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, ImmutableCString&& tag) { - auto const& gl = mContext; samples = std::clamp(samples, uint8_t(1u), uint8_t(gl.gets.max_samples)); GLTexture* t = handle_cast(th); @@ -1480,7 +1487,7 @@ void OpenGLDriver::importTextureR(Handle th, intptr_t const id, // subsequent backend APIs can handle operations based on this setting. construct(th, target, levels, samples, width, height, depth, format, usage, false); - importTextureCommon(th, id, target, levels, format, samples, width, height, depth, usage, + importTextureCommon(getBackendState(), th, id, target, levels, format, samples, width, height, depth, usage, std::move(tag)); } @@ -1500,7 +1507,7 @@ void OpenGLDriver::importTextureAsyncR(Handle th, intptr_t const id, getJobQueue()->push([this, th, id, target, levels, format, samples, width, height, depth, usage, handler, callback, user, tag=std::move(tag)]() mutable { DEBUG_MARKER_NAME("importTextureAsyncR") - importTextureCommon(th, id, target, levels, format, samples, width, height, depth, usage, + importTextureCommon(getWorkerState(), th, id, target, levels, format, samples, width, height, depth, usage, std::move(tag)); // glFlush() should be called when using a shared context for this operation. Without it, // the driver may delay submitting commands to the GPU, preventing other contexts from @@ -1513,7 +1520,7 @@ void OpenGLDriver::importTextureAsyncR(Handle th, intptr_t const id, void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer const* vb) { // NOTE: this is called often and must be as efficient as possible. - auto& gl = mContext; + auto& gl = getBackendState(); #ifndef NDEBUG if (UTILS_LIKELY(gl.ext.OES_vertex_array_object)) { @@ -1674,7 +1681,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo, break; } - auto& gl = mContext; + auto& gl = getBackendState(); GLenum target = GL_TEXTURE_2D; if (any(t->usage & TextureUsage::SAMPLEABLE)) { @@ -1884,7 +1891,7 @@ void OpenGLDriver::renderBufferStorage(GLuint const rbo, GLenum internalformat, if (samples > 1) { #ifndef __EMSCRIPTEN__ #ifdef GL_EXT_multisampled_render_to_texture - auto& gl = mContext; + auto& gl = getBackendState(); if (gl.ext.EXT_multisampled_render_to_texture || gl.ext.EXT_multisampled_render_to_texture2) { glext::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, @@ -2166,7 +2173,7 @@ void OpenGLDriver::createSwapChainHeadlessR(Handle sch, void OpenGLDriver::createTimerQueryR(Handle tqh, ImmutableCString&& tag) { DEBUG_MARKER() GLTimerQuery* tq = handle_cast(tqh); - mContext.createTimerQuery(tq); + getBackendState().createTimerQuery(tq); mHandleAllocator.associateTagToHandle(tqh.getId(), std::move(tag)); } @@ -2181,7 +2188,7 @@ void OpenGLDriver::createDescriptorSetR(Handle dsh, Handle dslh, ImmutableCString&& tag) { DEBUG_MARKER() GLDescriptorSetLayout const* dsl = handle_cast(dslh); - construct(dsh, mContext, dslh, dsl); + construct(dsh, getBackendState(), dslh, dsl); mHandleAllocator.associateTagToHandle(dsh.getId(), std::move(tag)); } @@ -2189,7 +2196,7 @@ void OpenGLDriver::mapBufferR(MemoryMappedBufferHandle mmbh, BufferObjectHandle boh, size_t offset, size_t size, MapBufferAccessFlags access, ImmutableCString&& tag) { DEBUG_MARKER() - construct(mmbh, mContext, mHandleAllocator, boh, offset, size, access); + construct(mmbh, getBackendState(), mHandleAllocator, boh, offset, size, access); mHandleAllocator.associateTagToHandle(mmbh.getId(), std::move(tag)); } @@ -2213,9 +2220,8 @@ void OpenGLDriver::destroyVertexBuffer(Handle vbh) { } } -void OpenGLDriver::destroyIndexBufferCommon(Handle ibh) { +void OpenGLDriver::destroyIndexBufferCommon(OpenGLState& gl, Handle ibh) { GLIndexBuffer const* ib = handle_cast(ibh); - auto& gl = mContext; gl.deleteBuffer(ib->gl.buffer, GL_ELEMENT_ARRAY_BUFFER); destruct(ibh, ib); } @@ -2227,17 +2233,16 @@ void OpenGLDriver::destroyIndexBuffer(Handle ibh) { GLIndexBuffer const* ib = handle_cast(ibh); if (ib->asynchronous) { getJobQueue()->push([this, ibh]() { - destroyIndexBufferCommon(ibh); + destroyIndexBufferCommon(getWorkerState(), ibh); }); } else { - destroyIndexBufferCommon(ibh); + destroyIndexBufferCommon(getBackendState(), ibh); } } } -void OpenGLDriver::destroyBufferObjectCommon(Handle boh) { +void OpenGLDriver::destroyBufferObjectCommon(OpenGLState& gl, Handle boh) { GLBufferObject const* bo = handle_cast(boh); - auto& gl = mContext; // check we're not destroying a buffer that has active mappings assert_invariant(bo->mappingCount == 0); if (UTILS_UNLIKELY(bo->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) { @@ -2255,10 +2260,10 @@ void OpenGLDriver::destroyBufferObject(Handle boh) { GLBufferObject const* bo = handle_cast(boh); if (bo->asynchronous) { getJobQueue()->push([this, boh]() { - destroyBufferObjectCommon(boh); + destroyBufferObjectCommon(getWorkerState(), boh); }); } else { - destroyBufferObjectCommon(boh); + destroyBufferObjectCommon(getBackendState(), boh); } } } @@ -2267,7 +2272,7 @@ void OpenGLDriver::destroyRenderPrimitive(Handle rph) { DEBUG_MARKER() if (rph) { - auto& gl = mContext; + auto& gl = getBackendState(); GLRenderPrimitive const* rp = handle_cast(rph); gl.deleteVertexArray(rp->gl.vao[gl.contextIndex]); @@ -2278,7 +2283,7 @@ void OpenGLDriver::destroyRenderPrimitive(Handle rph) { GLuint const nameInOtherContext = rp->gl.vao[otherContextIndex]; if (UTILS_UNLIKELY(nameInOtherContext)) { gl.destroyWithContext(otherContextIndex, - [name = nameInOtherContext](OpenGLContext& gl) { + [name = nameInOtherContext](OpenGLState& gl) { gl.deleteVertexArray(name); }); } @@ -2295,9 +2300,8 @@ void OpenGLDriver::destroyProgram(Handle ph) { } } -void OpenGLDriver::destroyTextureCommon(Handle th) { +void OpenGLDriver::destroyTextureCommon(OpenGLState& gl, Handle th) { GLTexture* t = handle_cast(th); - auto& gl = mContext; if (UTILS_LIKELY(!t->gl.imported)) { if (UTILS_LIKELY(t->usage & TextureUsage::SAMPLEABLE)) { // drop a reference @@ -2347,10 +2351,10 @@ void OpenGLDriver::destroyTexture(Handle th) { GLTexture* t = handle_cast(th); if (t->asynchronous) { getJobQueue()->push([this, th]() { - destroyTextureCommon(th); + destroyTextureCommon(getWorkerState(), th); }); } else { - destroyTextureCommon(th); + destroyTextureCommon(getBackendState(), th); } } } @@ -2359,7 +2363,7 @@ void OpenGLDriver::destroyRenderTarget(Handle rth) { DEBUG_MARKER() if (rth) { - auto& gl = mContext; + auto& gl = getBackendState(); GLRenderTarget const* rt = handle_cast(rth); if (rt->gl.fbo) { // first unbind this framebuffer if needed @@ -2449,7 +2453,7 @@ void OpenGLDriver::destroyTimerQuery(Handle tqh) { if (tqh) { GLTimerQuery* tq = handle_cast(tqh); - mContext.destroyTimerQuery(tq); + getBackendState().destroyTimerQuery(tq); destruct(tqh, tq); } } @@ -2480,7 +2484,7 @@ void OpenGLDriver::unmapBuffer(MemoryMappedBufferHandle mmbh) { DEBUG_MARKER() if (mmbh) { GLMemoryMappedBuffer* const mmb = handle_cast(mmbh); - mmb->unmap(mContext, mHandleAllocator); + mmb->unmap(getBackendState(), mHandleAllocator); destruct(mmbh, mmb); } } @@ -2775,7 +2779,7 @@ bool OpenGLDriver::isRenderTargetFormatSupported(TextureFormat const format) { // Supported formats per http://docs.gl/es3/glRenderbufferStorage, note that desktop OpenGL may // support more formats, but it requires querying GL_INTERNALFORMAT_SUPPORTED which is not // available in OpenGL ES. - auto const& gl = mContext; + auto const& gl = getBackendState(); if (UTILS_UNLIKELY(gl.isES2())) { auto [es2format, type] = textureFormatToFormatAndType(format); return es2format != GL_NONE && type != GL_NONE; @@ -2851,7 +2855,7 @@ bool OpenGLDriver::isRenderTargetFormatSupported(TextureFormat const format) { } bool OpenGLDriver::isFrameBufferFetchSupported() { - auto const& gl = mContext; + auto const& gl = getBackendState(); return gl.ext.EXT_shader_framebuffer_fetch; } @@ -3078,7 +3082,7 @@ void OpenGLDriver::makeCurrent(Handle schDraw, Handle } } // OpenGL context is about to change, unbind everything - mContext.unbindEverything(); + getBackendState().unbindEverything(); }, [this](size_t const index) { for (auto const t: mTexturesWithStreamsAttached) { @@ -3090,7 +3094,7 @@ void OpenGLDriver::makeCurrent(Handle schDraw, Handle glGenTextures(1, &t->gl.id); } mPlatform.attach(t->hwStream->stream, t->gl.id); - mContext.updateTexImage(GL_TEXTURE_EXTERNAL_OES, t->gl.id); + getBackendState().updateTexImage(GL_TEXTURE_EXTERNAL_OES, t->gl.id); } } @@ -3100,7 +3104,7 @@ void OpenGLDriver::makeCurrent(Handle schDraw, Handle mInvalidDescriptorSetBindings |= changed; // OpenGL context has changed, resynchronize the state with the cache - mContext.synchronizeStateAndCache(index); + getBackendState().synchronizeStateAndCache(index); DLOG(INFO) << "*** OpenGL context change : " << (index ? "protected" : "default"); }); @@ -3110,8 +3114,8 @@ void OpenGLDriver::makeCurrent(Handle schDraw, Handle // When a GL context is first attached to a window, width and height are set to the // dimensions of that window. // So basically, our viewport/scissor can be reset to "something" here. - mContext.state.window.viewport = {}; - mContext.state.window.scissor = {}; + getBackendState().state.window.viewport = {}; + getBackendState().state.window.scissor = {}; } // ------------------------------------------------------------------------------------------------ @@ -3159,9 +3163,8 @@ void OpenGLDriver::setVertexBufferObjectAsyncR(AsyncCallId jobId, Handle ibh, BufferDescriptor&& p, +void OpenGLDriver::updateIndexBufferCommon(OpenGLState& gl, Handle ibh, BufferDescriptor&& p, uint32_t const byteOffset) { - auto& gl = mContext; GLIndexBuffer const* ib = handle_cast(ibh); assert_invariant(ib->elementSize == 2 || ib->elementSize == 4); @@ -3177,7 +3180,7 @@ void OpenGLDriver::updateIndexBufferCommon(Handle ibh, BufferDesc void OpenGLDriver::updateIndexBuffer( Handle ibh, BufferDescriptor&& p, uint32_t const byteOffset) { DEBUG_MARKER() - updateIndexBufferCommon(ibh, std::move(p), byteOffset); + updateIndexBufferCommon(getBackendState(), ibh, std::move(p), byteOffset); } void OpenGLDriver::updateIndexBufferAsyncR(AsyncCallId jobId, Handle ibh, @@ -3186,7 +3189,7 @@ void OpenGLDriver::updateIndexBufferAsyncR(AsyncCallId jobId, Handlepush([this, ibh, p=std::move(p), byteOffset, handler, callback, user]() mutable { DEBUG_MARKER_NAME("updateIndexBufferAsyncR") - updateIndexBufferCommon(ibh, std::move(p), byteOffset); + updateIndexBufferCommon(getWorkerState(), ibh, std::move(p), byteOffset); // glFlush() should be called when using a shared context for this operation. Without it, // the driver may delay submitting commands to the GPU, preventing other contexts from // seeing the changes immediately. This ensures submitting the current commands right away. @@ -3196,9 +3199,7 @@ void OpenGLDriver::updateIndexBufferAsyncR(AsyncCallId jobId, Handle boh, BufferDescriptor&& bd, uint32_t const byteOffset) { - - auto& gl = mContext; + OpenGLState& gl, Handle boh, BufferDescriptor&& bd, uint32_t const byteOffset) { GLBufferObject* bo = handle_cast(boh); assert_invariant(bd.size + byteOffset <= bo->byteCount); @@ -3232,7 +3233,7 @@ void OpenGLDriver::updateBufferObjectCommon( void OpenGLDriver::updateBufferObject( Handle boh, BufferDescriptor&& bd, uint32_t const byteOffset) { DEBUG_MARKER() - updateBufferObjectCommon(boh, std::move(bd), byteOffset); + updateBufferObjectCommon(getBackendState(), boh, std::move(bd), byteOffset); } void OpenGLDriver::updateBufferObjectAsyncR(AsyncCallId jobId, Handle boh, @@ -3241,7 +3242,7 @@ void OpenGLDriver::updateBufferObjectAsyncR(AsyncCallId jobId, Handlepush([this, boh, bd=std::move(bd), byteOffset, handler, callback, user]() mutable { DEBUG_MARKER_NAME("updateBufferObjectAsyncR") - updateBufferObjectCommon(boh, std::move(bd), byteOffset); + updateBufferObjectCommon(getWorkerState(), boh, std::move(bd), byteOffset); // glFlush() should be called when using a shared context for this operation. Without it, // the driver may delay submitting commands to the GPU, preventing other contexts from // seeing the changes immediately. This ensures submitting the current commands right away. @@ -3272,7 +3273,7 @@ void OpenGLDriver::updateBufferObjectUnsynchronized( // TODO: use updateBuffer() for all types of buffer? Make sure GL supports that. updateBufferObject(boh, std::move(bd), byteOffset); } else { - auto& gl = mContext; + auto& gl = getBackendState(); gl.bindBuffer(bo->gl.binding, bo->gl.id); retry: void* const vaddr = glMapBufferRange(bo->gl.binding, byteOffset, GLsizeiptr(bd.size), @@ -3301,7 +3302,7 @@ retry: void OpenGLDriver::resetBufferObject(Handle boh) { DEBUG_MARKER() - auto& gl = mContext; + auto& gl = getBackendState(); GLBufferObject const* bo = handle_cast(boh); if (UTILS_UNLIKELY(bo->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) { @@ -3313,16 +3314,16 @@ void OpenGLDriver::resetBufferObject(Handle boh) { } } -void OpenGLDriver::update3DImageCommon(Handle th, +void OpenGLDriver::update3DImageCommon(OpenGLState& gl, Handle th, uint32_t const level, uint32_t const xoffset, uint32_t const yoffset, uint32_t const zoffset, uint32_t const width, uint32_t const height, uint32_t const depth, PixelBufferDescriptor&& data) { GLTexture const* t = handle_cast(th); if (data.type == PixelDataType::COMPRESSED) { - setCompressedTextureData(t, + setCompressedTextureData(gl, t, level, xoffset, yoffset, zoffset, width, height, depth, std::move(data)); } else { - setTextureData(t, + setTextureData(gl, t, level, xoffset, yoffset, zoffset, width, height, depth, std::move(data)); } } @@ -3332,7 +3333,7 @@ void OpenGLDriver::update3DImage(Handle th, uint32_t const width, uint32_t const height, uint32_t const depth, PixelBufferDescriptor&& data) { DEBUG_MARKER() - update3DImageCommon(th, level, xoffset, yoffset, zoffset, width, height, depth, + update3DImageCommon(getBackendState(), th, level, xoffset, yoffset, zoffset, width, height, depth, std::move(data)); } @@ -3344,7 +3345,7 @@ void OpenGLDriver::update3DImageAsyncR(AsyncCallId jobId, Handle th, getJobQueue()->push([this, th, level, xoffset, yoffset, zoffset, width, height, depth, data=std::move(data), handler, callback, user]() mutable { DEBUG_MARKER_NAME("update3DImageAsync") - update3DImageCommon(th, level, xoffset, yoffset, zoffset, width, height, depth, + update3DImageCommon(getWorkerState(), th, level, xoffset, yoffset, zoffset, width, height, depth, std::move(data)); // glFlush() should be called when using a shared context for this operation. Without it, // the driver may delay submitting commands to the GPU, preventing other contexts from @@ -3357,7 +3358,7 @@ void OpenGLDriver::update3DImageAsyncR(AsyncCallId jobId, Handle th, void OpenGLDriver::generateMipmaps(Handle th) { DEBUG_MARKER() - auto& gl = mContext; + auto& gl = getBackendState(); GLTexture const* t = handle_cast(th); #if defined(BACKEND_OPENGL_LEVEL_GLES31) assert_invariant(t->gl.target != GL_TEXTURE_2D_MULTISAMPLE); @@ -3372,11 +3373,10 @@ void OpenGLDriver::generateMipmaps(Handle th) { CHECK_GL_ERROR() } -void OpenGLDriver::setTextureData(GLTexture const* t, uint32_t const level, +void OpenGLDriver::setTextureData(OpenGLState& gl, GLTexture const* t, uint32_t const level, uint32_t const xoffset, uint32_t const yoffset, uint32_t const zoffset, uint32_t const width, uint32_t const height, uint32_t const depth, PixelBufferDescriptor&& p) { - auto& gl = mContext; assert_invariant(t != nullptr); assert_invariant(xoffset + width <= std::max(1u, t->width >> level)); @@ -3390,7 +3390,7 @@ void OpenGLDriver::setTextureData(GLTexture const* t, uint32_t const level, GLenum glFormat; GLenum glType; - if (mContext.isES2()) { + if (gl.isES2()) { auto const formatAndType = textureFormatToFormatAndType(t->format); glFormat = formatAndType.first; glType = formatAndType.second; @@ -3422,7 +3422,7 @@ void OpenGLDriver::setTextureData(GLTexture const* t, uint32_t const level, // fallthrough... case SamplerType::SAMPLER_2D: // NOTE: GL_TEXTURE_2D_MULTISAMPLE is not allowed - bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t); + gl.bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t->gl.target, t->gl.id, t->gl.external); gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); assert_invariant(t->gl.target == GL_TEXTURE_2D); glTexSubImage2D(t->gl.target, GLint(level), @@ -3433,7 +3433,7 @@ void OpenGLDriver::setTextureData(GLTexture const* t, uint32_t const level, assert_invariant(!gl.isES2()); #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 assert_invariant(zoffset + depth <= std::max(1u, t->depth >> level)); - bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t); + gl.bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t->gl.target, t->gl.id, t->gl.external); gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); assert_invariant(t->gl.target == GL_TEXTURE_3D); glTexSubImage3D(t->gl.target, GLint(level), @@ -3447,7 +3447,7 @@ void OpenGLDriver::setTextureData(GLTexture const* t, uint32_t const level, #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 assert_invariant(zoffset + depth <= t->depth); // NOTE: GL_TEXTURE_2D_MULTISAMPLE is not allowed - bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t); + gl.bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t->gl.target, t->gl.id, t->gl.external); gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); assert_invariant(t->gl.target == GL_TEXTURE_2D_ARRAY || t->gl.target == GL_TEXTURE_CUBE_MAP_ARRAY); @@ -3458,7 +3458,7 @@ void OpenGLDriver::setTextureData(GLTexture const* t, uint32_t const level, break; case SamplerType::SAMPLER_CUBEMAP: { assert_invariant(t->gl.target == GL_TEXTURE_CUBE_MAP); - bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t); + gl.bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t->gl.target, t->gl.id, t->gl.external); gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); assert_invariant(width == height); @@ -3481,11 +3481,10 @@ void OpenGLDriver::setTextureData(GLTexture const* t, uint32_t const level, CHECK_GL_ERROR() } -void OpenGLDriver::setCompressedTextureData(GLTexture const* t, uint32_t const level, +void OpenGLDriver::setCompressedTextureData(OpenGLState& gl, GLTexture const* t, uint32_t const level, uint32_t const xoffset, uint32_t const yoffset, uint32_t const zoffset, uint32_t const width, uint32_t const height, uint32_t const depth, PixelBufferDescriptor&& p) { - auto& gl = mContext; assert_invariant(xoffset + width <= std::max(1u, t->width >> level)); assert_invariant(yoffset + height <= std::max(1u, t->height >> level)); @@ -3510,7 +3509,7 @@ void OpenGLDriver::setCompressedTextureData(GLTexture const* t, uint32_t const l // fallthrough... case SamplerType::SAMPLER_2D: // NOTE: GL_TEXTURE_2D_MULTISAMPLE is not allowed - bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t); + gl.bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t->gl.target, t->gl.id, t->gl.external); gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); assert_invariant(t->gl.target == GL_TEXTURE_2D); glCompressedTexSubImage2D(t->gl.target, GLint(level), @@ -3521,7 +3520,7 @@ void OpenGLDriver::setCompressedTextureData(GLTexture const* t, uint32_t const l case SamplerType::SAMPLER_3D: assert_invariant(!gl.isES2()); #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t); + gl.bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t->gl.target, t->gl.id, t->gl.external); gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); assert_invariant(t->gl.target == GL_TEXTURE_3D); glCompressedTexSubImage3D(t->gl.target, GLint(level), @@ -3544,7 +3543,7 @@ void OpenGLDriver::setCompressedTextureData(GLTexture const* t, uint32_t const l break; case SamplerType::SAMPLER_CUBEMAP: { assert_invariant(t->gl.target == GL_TEXTURE_CUBE_MAP); - bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t); + gl.bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t->gl.target, t->gl.id, t->gl.external); gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); assert_invariant(width == height); @@ -3576,7 +3575,7 @@ void OpenGLDriver::setupExternalImage(void* image) { } void OpenGLDriver::setExternalStream(Handle th, Handle sh) { - auto const& gl = mContext; + auto const& gl = getBackendState(); if (gl.ext.OES_EGL_image_external_essl3) { DEBUG_MARKER() @@ -3606,7 +3605,7 @@ void OpenGLDriver::attachStream(GLTexture* t, GLStream* hwStream) { switch (hwStream->streamType) { case StreamType::NATIVE: mPlatform.attach(hwStream->stream, t->gl.id); - mContext.updateTexImage(GL_TEXTURE_EXTERNAL_OES, t->gl.id); + getBackendState().updateTexImage(GL_TEXTURE_EXTERNAL_OES, t->gl.id); break; case StreamType::ACQUIRED: break; @@ -3616,7 +3615,7 @@ void OpenGLDriver::attachStream(GLTexture* t, GLStream* hwStream) { UTILS_NOINLINE void OpenGLDriver::detachStream(GLTexture* t) noexcept { - auto& gl = mContext; + auto& gl = getBackendState(); auto& texturesWithStreamsAttached = mTexturesWithStreamsAttached; auto const pos = std::find(texturesWithStreamsAttached.begin(), texturesWithStreamsAttached.end(), t); if (pos != texturesWithStreamsAttached.end()) { @@ -3673,7 +3672,7 @@ void OpenGLDriver::replaceStream(GLTexture* texture, GLStream* newStream) noexce glGenTextures(1, &texture->gl.id); } mPlatform.attach(newStream->stream, texture->gl.id); - mContext.updateTexImage(GL_TEXTURE_EXTERNAL_OES, texture->gl.id); + getBackendState().updateTexImage(GL_TEXTURE_EXTERNAL_OES, texture->gl.id); break; case StreamType::ACQUIRED: // Just re-use the old texture id. @@ -3686,13 +3685,13 @@ void OpenGLDriver::replaceStream(GLTexture* texture, GLStream* newStream) noexce void OpenGLDriver::beginTimerQuery(Handle tqh) { DEBUG_MARKER() GLTimerQuery* tq = handle_cast(tqh); - mContext.beginTimeElapsedQuery(tq); + getBackendState().beginTimeElapsedQuery(tq); } void OpenGLDriver::endTimerQuery(Handle tqh) { DEBUG_MARKER() GLTimerQuery* tq = handle_cast(tqh); - mContext.endTimeElapsedQuery(*this, tq); + getBackendState().endTimeElapsedQuery(*this, tq); } TimerQueryResult OpenGLDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { @@ -3713,7 +3712,7 @@ void OpenGLDriver::beginRenderPass(Handle rth, getShaderCompilerService().tick(); - auto& gl = mContext; + auto& gl = getBackendState(); mRenderPassTarget = rth; mRenderPassParams = params; @@ -3787,7 +3786,7 @@ void OpenGLDriver::beginRenderPass(Handle rth, void OpenGLDriver::endRenderPass(int) { DEBUG_MARKER() - auto& gl = mContext; + auto& gl = getBackendState(); assert_invariant(mRenderPassTarget); // endRenderPass() called without beginRenderPass()? @@ -3835,8 +3834,8 @@ void OpenGLDriver::endRenderPass(int) { #ifndef NDEBUG // clear the discarded buffers in debug builds - mContext.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo); - mContext.disable(GL_SCISSOR_TEST); + getBackendState().bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo); + getBackendState().disable(GL_SCISSOR_TEST); clearWithRasterPipe(discardFlags, { 0, 1, 0, 1 }, 1.0, 0); #endif @@ -3858,7 +3857,7 @@ void OpenGLDriver::resolvePass(ResolveAction const action, GLRenderTarget const* #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 assert_invariant(rt->gl.fbo_read); - auto& gl = mContext; + auto& gl = getBackendState(); const TargetBufferFlags resolve = rt->gl.resolve & ~discardFlags; GLbitfield const mask = getAttachmentBitfield(resolve); if (UTILS_UNLIKELY(mask)) { @@ -3937,7 +3936,7 @@ GLsizei OpenGLDriver::getAttachments(AttachmentArray& attachments, void OpenGLDriver::setScissor(Viewport const& scissor) noexcept { constexpr uint32_t maxvalu = std::numeric_limits::max(); - auto& gl = mContext; + auto& gl = getBackendState(); // TODO: disable scissor when it is bigger than the current surface? if (scissor.left == 0 && scissor.bottom == 0 && @@ -3959,7 +3958,7 @@ void OpenGLDriver::setScissor(Viewport const& scissor) noexcept { void OpenGLDriver::insertEventMarker(char const* string) { #ifndef __EMSCRIPTEN__ #ifdef GL_EXT_debug_marker - auto const& gl = mContext; + auto const& gl = getBackendState(); if (gl.ext.EXT_debug_marker) { glInsertEventMarkerEXT(GLsizei(strlen(string)), string); } @@ -4013,7 +4012,7 @@ void OpenGLDriver::stopCapture(int) { void OpenGLDriver::readPixelsFromBoundFramebuffer(uint32_t const x, uint32_t const y, uint32_t width, uint32_t height, PixelBufferDescriptor&& p) { - auto& gl = mContext; + auto& gl = getBackendState(); GLenum const glFormat = getFormat(p.format); GLenum const glType = getType(p.type); @@ -4090,7 +4089,7 @@ void OpenGLDriver::readPixelsFromBoundFramebuffer(uint32_t const x, uint32_t con auto* const pUserBuffer = new PixelBufferDescriptor(std::move(p)); whenGpuCommandsComplete([this, width, height, pbo, pboSize, pUserBuffer]() mutable { PixelBufferDescriptor& p = *pUserBuffer; - auto& gl = mContext; + auto& gl = getBackendState(); gl.bindBuffer(GL_PIXEL_PACK_BUFFER, pbo); void const* vaddr = nullptr; #if defined(__EMSCRIPTEN__) @@ -4131,7 +4130,7 @@ void OpenGLDriver::readPixelsFromBoundFramebuffer(uint32_t const x, uint32_t con void OpenGLDriver::readPixels(Handle src, uint32_t const x, uint32_t const y, uint32_t width, uint32_t height, PixelBufferDescriptor&& p) { DEBUG_MARKER() - auto& gl = mContext; + auto& gl = getBackendState(); GLRenderTarget const* s = handle_cast(src); @@ -4152,7 +4151,7 @@ void OpenGLDriver::readPixels(Handle src, uint32_t const x, uint void OpenGLDriver::readTexture(Handle src, uint8_t level, uint16_t layer, uint32_t x, uint32_t y, uint32_t width, uint32_t height, PixelBufferDescriptor&& p) { DEBUG_MARKER() - auto& gl = mContext; + auto& gl = getBackendState(); // readTexture() requires GLES 3.0+ features such as GL_READ_FRAMEBUFFER and // glFramebufferTextureLayer, so we wrap it in FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2. @@ -4209,7 +4208,7 @@ void OpenGLDriver::readTexture(Handle src, uint8_t level, uint16_t la void OpenGLDriver::readBufferSubData(BufferObjectHandle boh, uint32_t const offset, uint32_t size, BufferDescriptor&& p) { - UTILS_UNUSED_IN_RELEASE auto& gl = mContext; + UTILS_UNUSED_IN_RELEASE auto& gl = getBackendState(); assert_invariant(!gl.isES2()); #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 @@ -4233,7 +4232,7 @@ void OpenGLDriver::readBufferSubData(BufferObjectHandle boh, auto* pUserBuffer = new BufferDescriptor(std::move(p)); whenGpuCommandsComplete([this, size, pbo, pUserBuffer]() mutable { BufferDescriptor& p = *pUserBuffer; - auto& gl = mContext; + auto& gl = getBackendState(); gl.bindBuffer(GL_PIXEL_PACK_BUFFER, pbo); if (void const* vaddr = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, size, GL_MAP_READ_BIT)) { memcpy(p.buffer, vaddr, size); @@ -4339,7 +4338,7 @@ void OpenGLDriver::beginFrame( UTILS_UNUSED int64_t const refreshIntervalNs, UTILS_UNUSED uint32_t const frameId) { PROFILE_MARKER(PROFILE_NAME_BEGINFRAME) - auto& gl = mContext; + auto& gl = getBackendState(); insertEventMarker("beginFrame"); mPlatform.beginFrame(monotonic_clock_ns, refreshIntervalNs, frameId); if (UTILS_UNLIKELY(!mTexturesWithStreamsAttached.empty())) { @@ -4386,7 +4385,7 @@ void OpenGLDriver::endFrame(UTILS_UNUSED uint32_t const frameId) { // WebGL builds are single-threaded so users might manipulate various GL state after we're // done with the frame. We do NOT officially support using Filament in this way, but we can // at least do some minimal safety things here, such as resetting the VAO to 0. - auto& gl = mContext; + auto& gl = getBackendState(); gl.bindVertexArray(nullptr); for (int unit = OpenGLContext::DUMMY_TEXTURE_BINDING; unit >= 0; unit--) { gl.bindTexture(unit, GL_TEXTURE_2D, 0, false); @@ -4408,7 +4407,7 @@ void OpenGLDriver::updateDescriptorSetBuffer( uint32_t const offset, uint32_t const size) { GLDescriptorSet* ds = handle_cast(dsh); GLBufferObject* bo = boh ? handle_cast(boh) : nullptr; - ds->update(mContext, binding, bo, offset, size); + ds->update(getBackendState(), binding, bo, offset, size); } void OpenGLDriver::updateDescriptorSetTexture( @@ -4417,18 +4416,18 @@ void OpenGLDriver::updateDescriptorSetTexture( TextureHandle th, SamplerParams const params) { GLDescriptorSet* ds = handle_cast(dsh); - ds->update(mContext, mHandleAllocator, binding, th, params); + ds->update(getBackendState(), mHandleAllocator, binding, th, params); } void OpenGLDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_t offset, BufferDescriptor&& data) { GLMemoryMappedBuffer* const mmb = handle_cast(mmbh); - mmb->copy(mContext, *this, offset, std::move(data)); + mmb->copy(getBackendState(), *this, offset, std::move(data)); } void OpenGLDriver::flush(int) { DEBUG_MARKER() - auto const& gl = mContext; + auto const& gl = getBackendState(); if (!gl.bugs.disable_glFlush) { glFlush(); } @@ -4455,13 +4454,13 @@ void OpenGLDriver::clearWithRasterPipe(TargetBufferFlags const clearFlags, float4 const& linearColor, GLfloat const depth, GLint const stencil) noexcept { if (any(clearFlags & TargetBufferFlags::COLOR_ALL)) { - mContext.colorMask(GL_TRUE); + getBackendState().colorMask(GL_TRUE); } if (any(clearFlags & TargetBufferFlags::DEPTH)) { - mContext.depthMask(GL_TRUE); + getBackendState().depthMask(GL_TRUE); } if (any(clearFlags & TargetBufferFlags::STENCIL)) { - mContext.stencilMaskSeparate(0xFF, mContext.state.stencil.back.stencilMask); + getBackendState().stencilMaskSeparate(0xFF, getBackendState().state.stencil.back.stencilMask); } #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 @@ -4549,7 +4548,7 @@ void OpenGLDriver::blit( Handle src, uint8_t const dstLevel, uint8_t const dstLayer, uint2 const srcOrigin, uint2 const size) { DEBUG_MARKER() - UTILS_UNUSED_IN_RELEASE auto& gl = mContext; + UTILS_UNUSED_IN_RELEASE auto& gl = getBackendState(); assert_invariant(!gl.isES2()); #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 @@ -4681,7 +4680,7 @@ void OpenGLDriver::blitDEPRECATED(TargetBufferFlags const buffers, // Note: blitDEPRECATED is only used by Renderer::copyFrame DEBUG_MARKER() - UTILS_UNUSED_IN_RELEASE auto& gl = mContext; + UTILS_UNUSED_IN_RELEASE auto& gl = getBackendState(); assert_invariant(!gl.isES2()); FILAMENT_CHECK_PRECONDITION(buffers == TargetBufferFlags::COLOR0) @@ -4742,7 +4741,7 @@ void OpenGLDriver::blitDEPRECATED(TargetBufferFlags const buffers, void OpenGLDriver::bindPipeline(PipelineState const& state) { DEBUG_MARKER() - auto& gl = mContext; + auto& gl = getBackendState(); setRasterState(state.rasterState); setStencilState(state.stencilState); gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant); @@ -4755,7 +4754,7 @@ void OpenGLDriver::bindPipeline(PipelineState const& state) { void OpenGLDriver::bindRenderPrimitive(Handle rph) { DEBUG_MARKER() - auto& gl = mContext; + auto& gl = getBackendState(); GLRenderPrimitive* const rp = handle_cast(rph); @@ -4819,7 +4818,7 @@ void OpenGLDriver::updateDescriptors(bitset8 const invalidDescriptorSets) noexce auto const offsetOnly = mInvalidDescriptorSetBindingOffsets & ~mInvalidDescriptorSetBindings; invalidDescriptorSets.forEachSetBit([this, offsetOnly, &boundDescriptorSets = mBoundDescriptorSets, - &context = mContext, + &state = getBackendState(), &boundProgram = *mBoundProgram](size_t const set) { assert_invariant(set < MAX_DESCRIPTOR_SET_COUNT); auto const& entry = boundDescriptorSets[set]; @@ -4832,7 +4831,7 @@ void OpenGLDriver::updateDescriptors(bitset8 const invalidDescriptorSets) noexce ds->validate(mHandleAllocator, mCurrentSetLayout[set]); } #endif - ds->bind(context, mHandleAllocator, boundProgram, + ds->bind(state, mHandleAllocator, boundProgram, set, entry.offsets.data(), offsetOnly[set]); } }); diff --git a/filament/backend/src/opengl/OpenGLDriver.h b/filament/backend/src/opengl/OpenGLDriver.h index bd01697aa2..18dcbed78d 100644 --- a/filament/backend/src/opengl/OpenGLDriver.h +++ b/filament/backend/src/opengl/OpenGLDriver.h @@ -74,6 +74,7 @@ namespace filament::backend { class OpenGLPlatform; +class OpenGLState; class PixelBufferDescriptor; struct TargetBufferInfo; class OpenGLProgram; @@ -224,11 +225,24 @@ public: private: OpenGLPlatform& mPlatform; OpenGLContext mContext; + OpenGLState* mBackendState = nullptr; // owned by backend thread + OpenGLState* mWorkerState = nullptr; // owned by worker thread (initialized for async mode) ShaderCompilerService mShaderCompilerService; friend class TimerQueryFactory; friend class TimerQueryNativeFactory; OpenGLContext& getContext() noexcept { return mContext; } + OpenGLState& getBackendState() const noexcept { + // mBackendState should always be available. + assert_invariant(mBackendState); + return *mBackendState; + } + OpenGLState& getWorkerState() const noexcept { + // mWorkerState should be available only when the asynchronous mode is enabled. + // Ideally, this method should be called within asynchronous jobs. + assert_invariant(mWorkerState); + return *mWorkerState; + } ShaderCompilerService& getShaderCompilerService() noexcept { return mShaderCompilerService; @@ -243,15 +257,15 @@ private: */ utils::CString getVendorString() const noexcept override { - return utils::CString{ mContext.state.vendor }; + return utils::CString{ mContext.vendor }; } utils::CString getRendererString() const noexcept override { - return utils::CString{ mContext.state.renderer }; + return utils::CString{ mContext.renderer }; } utils::CString getVersionString() const noexcept override { - return utils::CString{ mContext.state.version }; + return utils::CString{ mContext.version }; } JobQueue* getJobQueue() const noexcept { return mJobQueue.get(); } @@ -335,13 +349,13 @@ private: void setStencilState(StencilState ss) noexcept; - void setTextureData(GLTexture const* t, + void setTextureData(OpenGLState& gl, GLTexture const* t, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, PixelBufferDescriptor&& p); - void setCompressedTextureData(GLTexture const* t, + void setCompressedTextureData(OpenGLState& gl, GLTexture const* t, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, @@ -350,7 +364,7 @@ private: void renderBufferStorage(GLuint rbo, GLenum internalformat, uint32_t width, uint32_t height, uint8_t samples) const noexcept; - void textureStorage(GLTexture* t, uint32_t width, uint32_t height, + void textureStorage(OpenGLState& gl, GLTexture* t, uint32_t width, uint32_t height, uint32_t depth, bool useProtectedMemory) noexcept; /* State tracking GL wrappers... */ @@ -370,32 +384,32 @@ private: // Common methods void readPixelsFromBoundFramebuffer(uint32_t x, uint32_t y, uint32_t width, uint32_t height, PixelBufferDescriptor&& p); - void createTextureCommon(Handle th, SamplerType target, uint8_t levels, + void createTextureCommon(OpenGLState& gl, Handle th, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, utils::ImmutableCString&& tag); - void update3DImageCommon(Handle th, + void update3DImageCommon(OpenGLState& gl, Handle th, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, PixelBufferDescriptor&& data); void createTextureViewSwizzleCommon(Handle th, Handle srch, TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a, utils::ImmutableCString&& tag); - void importTextureCommon(Handle th, intptr_t id, SamplerType target, uint8_t levels, + void importTextureCommon(OpenGLState& gl, Handle th, intptr_t id, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, utils::ImmutableCString&& tag); - void createBufferObjectCommon(Handle boh, uint32_t byteCount, + void createBufferObjectCommon(OpenGLState& gl, Handle boh, uint32_t byteCount, BufferObjectBinding bindingType, BufferUsage usage, utils::ImmutableCString&& tag); void setVertexBufferObjectCommon(Handle vbh, uint32_t index, Handle boh); - void updateBufferObjectCommon(Handle boh, BufferDescriptor&& bd, + void updateBufferObjectCommon(OpenGLState& gl, Handle boh, BufferDescriptor&& bd, uint32_t byteOffset); - void createIndexBufferCommon(Handle ibh, ElementType const elementType, + void createIndexBufferCommon(OpenGLState& gl, Handle ibh, ElementType const elementType, uint32_t indexCount, BufferUsage const usage, utils::ImmutableCString&& tag); - void updateIndexBufferCommon(Handle ibh, BufferDescriptor&& p, + void updateIndexBufferCommon(OpenGLState& gl, Handle ibh, BufferDescriptor&& p, uint32_t const byteOffset); - void destroyTextureCommon(Handle th); - void destroyBufferObjectCommon(Handle boh); - void destroyIndexBufferCommon(Handle ibh); + void destroyTextureCommon(OpenGLState& gl, Handle th); + void destroyBufferObjectCommon(OpenGLState& gl, Handle boh); + void destroyIndexBufferCommon(OpenGLState& gl, Handle ibh); // state required to represent the current render pass Handle mRenderPassTarget; diff --git a/filament/backend/src/opengl/OpenGLProgram.cpp b/filament/backend/src/opengl/OpenGLProgram.cpp index 491f419fd1..ef1becbfd9 100644 --- a/filament/backend/src/opengl/OpenGLProgram.cpp +++ b/filament/backend/src/opengl/OpenGLProgram.cpp @@ -112,7 +112,7 @@ void OpenGLProgram::initialize(OpenGLDriver& gld) { assert_invariant(mToken == nullptr); if (gl.program) { assert_invariant(lazyInitializationData); - initializeProgramState(gld.getContext(), gl.program, *lazyInitializationData); + initializeProgramState(gld.getBackendState(), gl.program, *lazyInitializationData); delete lazyInitializationData; } } @@ -121,7 +121,7 @@ void OpenGLProgram::initialize(OpenGLDriver& gld) { * Initializes our internal state from a valid program. This must only be called after * checkProgramStatus() has been successfully called. */ -void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint program, +void OpenGLProgram::initializeProgramState(OpenGLState& gls, GLuint program, LazyInitializationData& lazyInitializationData) { FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT); @@ -139,7 +139,7 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra GLuint binding = 0; // needed for samplers - context.useProgram(program); + gls.useProgram(program); UTILS_NOUNROLL for (descriptor_set_t set = 0; set < MAX_DESCRIPTOR_SET_COUNT; set++) { @@ -149,7 +149,7 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra case DescriptorType::SHADER_STORAGE_BUFFER: { if (!entry.name.empty()) { #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - if (UTILS_LIKELY(!context.isES2())) { + if (UTILS_LIKELY(!gls.isES2())) { GLuint const index = glGetUniformBlockIndex(program, entry.name.c_str()); if (index != GL_INVALID_INDEX) { @@ -219,7 +219,7 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra CHECK_GL_ERROR() } - if (context.isES2()) { + if (gls.isES2()) { // ES2 initialization of (fake) UBOs UniformsRecord* const uniformsRecords = new(std::nothrow) UniformsRecord[Program::UNIFORM_BINDING_COUNT]; UTILS_NOUNROLL diff --git a/filament/backend/src/opengl/OpenGLProgram.h b/filament/backend/src/opengl/OpenGLProgram.h index c5ceccfbc0..c6148602ca 100644 --- a/filament/backend/src/opengl/OpenGLProgram.h +++ b/filament/backend/src/opengl/OpenGLProgram.h @@ -20,7 +20,7 @@ #include "DriverBase.h" #include "BindingMap.h" -#include "OpenGLContext.h" +#include "OpenGLState.h" #include "ShaderCompilerService.h" #include @@ -56,7 +56,7 @@ public: bool isValid() const noexcept { return mToken || gl.program != 0; } - bool use(OpenGLDriver* const gld, OpenGLContext& context) noexcept { + bool use(OpenGLDriver* const gld, OpenGLState& gls) noexcept { // both non-null is impossible by construction assert_invariant(!mToken || !gl.program); @@ -71,7 +71,7 @@ public: return false; } - context.useProgram(gl.program); + gls.useProgram(gl.program); return true; } @@ -105,7 +105,7 @@ private: void initialize(OpenGLDriver& gld); - void initializeProgramState(OpenGLContext& context, GLuint program, + void initializeProgramState(OpenGLState& gls, GLuint program, LazyInitializationData& lazyInitializationData); BindingMap mBindingMap; // 8 bytes + out-of-line 256 bytes diff --git a/filament/backend/src/opengl/OpenGLState.cpp b/filament/backend/src/opengl/OpenGLState.cpp new file mode 100644 index 0000000000..3e30eab7ae --- /dev/null +++ b/filament/backend/src/opengl/OpenGLState.cpp @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2026 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 "OpenGLState.h" + +#include "GLUtils.h" + +#include + +#include + +using namespace utils; + +namespace filament::backend { + +OpenGLState::OpenGLState(OpenGLContext& context) noexcept + : ext(context.ext), + bugs(context.bugs), + gets(context.gets), + procs(context.procs), + features(context.features), + mContext(context), + mTimerQueryFactory(TimerQueryFactory::init(mContext.getPlatform(), context)), + mSamplerMap(32) { + state.vao.p = &mDefaultVAO; + + // Push GL defaults to the new context + setDefaultState(); +} + +OpenGLState::~OpenGLState() noexcept { + delete mTimerQueryFactory; +} + +void OpenGLState::terminate() noexcept { +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 + if (!isES2()) { + for (auto& item: mSamplerMap) { + unbindSampler(item.second); + glDeleteSamplers(1, &item.second); + } + mSamplerMap.clear(); + } +#endif +} + +void OpenGLState::destroyWithContext( + size_t index, std::function const& closure) { + 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 OpenGLState::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 OpenGLState::synchronizeStateAndCache(size_t index) { + + // 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; + syncState(); +} + +void OpenGLState::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) + constexpr GLenum 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 + && mContext.getDriverConfig().stereoscopicType == StereoscopicType::INSTANCED) { + glEnable(GL_CLIP_DISTANCE0); + glEnable(GL_CLIP_DISTANCE1); + } +} + +GLuint OpenGLState::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(mContext.getPlatform().getDefaultFramebufferObject()); + } + // by construction, defaultFboForThisContext has a value. value_or() avoids a throwing call. + buffer = defaultFboForThisContext.value_or(0); + } + bindFramebufferResolved(target, buffer); + return buffer; +} + +void OpenGLState::unbindFramebuffer(GLenum target) noexcept { + bindFramebufferResolved(target, 0); +} + +void OpenGLState::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 OpenGLState::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 OpenGLState::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(mContext.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 OpenGLState::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) { + assert_invariant(state.textures.units[unit].target == target); + unbindTextureUnit(unit); + } + } + } +} + +void OpenGLState::unbindTextureUnit(GLuint unit) noexcept { + update_state(state.textures.units[unit].id, 0u, [&]() { + activeTexture(unit); + glBindTexture(state.textures.units[unit].target, 0u); + }); +} + +void OpenGLState::unbindSampler(GLuint sampler) noexcept { + // unbind this sampler from all the units it might be bound to + UTILS_NOUNROLL + for (GLuint unit = 0; unit < MAX_TEXTURE_UNIT_COUNT; unit++) { + if (state.textures.units[unit].sampler == sampler) { + bindSampler(unit, 0); + } + } +} + +void OpenGLState::deleteBuffer(GLuint buffer, GLenum target) noexcept { + glDeleteBuffers(1, &buffer); + + // bindings of bound buffers are reset to 0 + size_t const targetIndex = getIndexForBufferTarget(target); + auto& genericBinding = state.buffers.genericBinding[targetIndex]; + if (genericBinding == buffer) { + genericBinding = 0; + } + + if (UTILS_UNLIKELY(bugs.rebind_buffer_after_deletion)) { + if (genericBinding) { + glBindBuffer(target, genericBinding); + } + } + +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 + assert_invariant(mContext.getFeatureLevel() >= FeatureLevel::FEATURE_LEVEL_1 || + (target != GL_UNIFORM_BUFFER && target != GL_TRANSFORM_FEEDBACK_BUFFER)); + + if (target == GL_UNIFORM_BUFFER || target == GL_TRANSFORM_FEEDBACK_BUFFER) { + auto& indexedBinding = state.buffers.targets[targetIndex]; + UTILS_NOUNROLL + for (auto& entry: indexedBinding.buffers) { + if (entry.name == buffer) { + entry.name = 0; + entry.offset = 0; + entry.size = 0; + } + } + } +#endif +} + +void OpenGLState::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 OpenGLState::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() + mSamplerMap[params] = s; + return s; +} +#endif + + +void OpenGLState::syncState() 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 (mContext.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 (mContext.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 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 (mContext.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 (mContext.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 (mContext.major > 2) { +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 + glPixelStorei(GL_PACK_ROW_LENGTH, 0); +#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 OpenGLState::createTimerQuery(GLTimerQuery* query) { + mTimerQueryFactory->createTimerQuery(query); +} + +void OpenGLState::destroyTimerQuery(GLTimerQuery* query) { + mTimerQueryFactory->destroyTimerQuery(query); +} + +void OpenGLState::beginTimeElapsedQuery(GLTimerQuery* query) { + mTimerQueryFactory->beginTimeElapsedQuery(query); +} + +void OpenGLState::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) { + mTimerQueryFactory->endTimeElapsedQuery(driver, query); +} + +} // namespace filament::backend diff --git a/filament/backend/src/opengl/OpenGLState.h b/filament/backend/src/opengl/OpenGLState.h new file mode 100644 index 0000000000..680d61f65d --- /dev/null +++ b/filament/backend/src/opengl/OpenGLState.h @@ -0,0 +1,615 @@ +/* + * Copyright (C) 2026 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. + */ + +#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGLSTATE_H +#define TNT_FILAMENT_BACKEND_OPENGL_OPENGLSTATE_H + +#include "OpenGLContext.h" +#include "OpenGLTimerQuery.h" + +#include + +#include "gl_headers.h" + +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +#include +#include + +namespace filament::backend { + +class OpenGLDriver; + +class OpenGLState final : public TimerQueryFactoryInterface { +public: + using RenderPrimitive = OpenGLContext::RenderPrimitive; + using Extensions = OpenGLContext::Extensions; + using Bugs = OpenGLContext::Bugs; + using Gets = OpenGLContext::Gets; + using Procs = OpenGLContext::Procs; + using vec4gli = OpenGLContext::vec4gli; + using vec2glf = OpenGLContext::vec2glf; + + static constexpr size_t MAX_TEXTURE_UNIT_COUNT = OpenGLContext::MAX_TEXTURE_UNIT_COUNT; + static constexpr size_t DUMMY_TEXTURE_BINDING = OpenGLContext::DUMMY_TEXTURE_BINDING; + static constexpr size_t MAX_BUFFER_BINDINGS = OpenGLContext::MAX_BUFFER_BINDINGS; + + explicit OpenGLState(OpenGLContext& context) noexcept; + ~OpenGLState() noexcept override; + + // TimerQueryInterface ------------------------------------------------------------------------ + + // note: OpenGLState being final ensures (clang) these are not called through the vtable + void createTimerQuery(GLTimerQuery* query) override; + void destroyTimerQuery(GLTimerQuery* query) override; + void beginTimeElapsedQuery(GLTimerQuery* query) override; + void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) override; + + // -------------------------------------------------------------------------------------------- + + // Immutable config forwarding (enables gl.ext.XXX, gl.bugs.XXX, etc) + Extensions const& ext; + Bugs const& bugs; + Gets const& gets; + Procs const& procs; + decltype(OpenGLContext::features) const& features; + + // Read-only query forwarding + bool isES2() const noexcept { return mContext.isES2(); } + bool hasFences() const noexcept { return mContext.hasFences(); } + FeatureLevel getFeatureLevel() const noexcept { return mContext.getFeatureLevel(); } + ShaderModel getShaderModel() const noexcept { return mContext.getShaderModel(); } + template + bool isAtLeastGL() const noexcept { return mContext.isAtLeastGL(); } + template + bool isAtLeastGLES() const noexcept { return mContext.isAtLeastGLES(); } + + OpenGLContext const& context() const noexcept { return mContext; } + + // -------------------------------------------------------------------------------------------- + + constexpr inline size_t getIndexForCap(GLenum cap) noexcept; + constexpr static inline size_t getIndexForBufferTarget(GLenum target) noexcept; + + void terminate() noexcept; + + void syncState() noexcept; + + inline void useProgram(GLuint program) noexcept; + + void pixelStore(GLenum, GLint) noexcept; + inline void activeTexture(GLuint unit) noexcept; + inline void bindTexture(GLuint unit, GLuint target, GLuint texId, bool external) noexcept; + + void unbindTexture(GLenum target, GLuint id) noexcept; + void unbindTextureUnit(GLuint unit) noexcept; + inline void bindVertexArray(RenderPrimitive const* p) noexcept; + inline void bindSampler(GLuint unit, GLuint sampler) noexcept; + void unbindSampler(GLuint sampler) noexcept; + + void bindBuffer(GLenum target, GLuint buffer) noexcept; + inline void bindBufferRange(GLenum target, GLuint index, GLuint buffer, + GLintptr offset, GLsizeiptr size) noexcept; + + GLuint bindFramebuffer(GLenum target, GLuint buffer) noexcept; + void unbindFramebuffer(GLenum target) noexcept; + + inline void enableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept; + inline void disableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept; + inline void enable(GLenum cap) noexcept; + inline void disable(GLenum cap) noexcept; + inline void frontFace(GLenum mode) noexcept; + inline void cullFace(GLenum mode) noexcept; + inline void blendEquation(GLenum modeRGB, GLenum modeA) noexcept; + inline void blendFunction(GLenum srcRGB, GLenum srcA, GLenum dstRGB, GLenum dstA) noexcept; + inline void colorMask(GLboolean flag) noexcept; + inline void depthMask(GLboolean flag) noexcept; + inline void depthFunc(GLenum func) noexcept; + inline void stencilFuncSeparate(GLenum funcFront, GLint refFront, GLuint maskFront, + GLenum funcBack, GLint refBack, GLuint maskBack) noexcept; + inline void stencilOpSeparate(GLenum sfailFront, GLenum dpfailFront, GLenum dppassFront, + GLenum sfailBack, GLenum dpfailBack, GLenum dppassBack) noexcept; + inline void stencilMaskSeparate(GLuint maskFront, GLuint maskBack) noexcept; + inline void polygonOffset(GLfloat factor, GLfloat units) noexcept; + + inline void setScissor(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept; + inline void viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept; + inline void depthRange(GLclampf near, GLclampf far) noexcept; + + void deleteBuffer(GLuint buffer, GLenum target) noexcept; + void deleteVertexArray(GLuint vao) noexcept; + + void destroyWithContext(size_t index, std::function const& closure); + + // state getters + vec4gli const& getViewport() const { return state.window.viewport; } + + // function to handle state changes we don't control + void updateTexImage(GLenum target, GLuint id) noexcept { + assert_invariant(target == GL_TEXTURE_EXTERNAL_OES); + // if another target is bound to this texture unit, unbind that texture + if (UTILS_UNLIKELY(state.textures.units[state.textures.active].target != target)) { + glBindTexture(state.textures.units[state.textures.active].target, 0); + state.textures.units[state.textures.active].target = GL_TEXTURE_EXTERNAL_OES; + } + // the texture is already bound to `target`, we just update our internal state + state.textures.units[state.textures.active].id = id; + } + void resetProgram() noexcept { state.program.use = 0; } + + void unbindEverything() noexcept; + void synchronizeStateAndCache(size_t index); + +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 + GLuint getSamplerSlow(SamplerParams sp) const noexcept; + + inline GLuint getSampler(SamplerParams sp) const noexcept { + assert_invariant(!sp.padding0); + assert_invariant(!sp.padding1); + assert_invariant(!sp.padding2); + auto& samplerMap = mSamplerMap; + auto pos = samplerMap.find(sp); + if (UTILS_UNLIKELY(pos == samplerMap.end())) { + return getSamplerSlow(sp); + } + return pos->second; + } +#endif + + // This is the index of the context in use. Must be either 0 (Unprotected) or 1 (Protected). + // This is used to manage the OpenGL name of ContainerObjects within each context. + uint32_t contextIndex = 0; + + // Try to keep the State structure sorted by data-access patterns + struct State { + State() noexcept = default; + // make sure we don't copy this state by accident + State(State const& rhs) = delete; + State(State&& rhs) noexcept = delete; + State& operator=(State const& rhs) = delete; + State& operator=(State&& rhs) noexcept = delete; + + GLuint draw_fbo = 0; + GLuint read_fbo = 0; + + struct { + GLuint use = 0; + } program; + + struct { + RenderPrimitive* p = nullptr; + } vao; + + struct { + GLenum frontFace = GL_CCW; + GLenum cullFace = GL_BACK; + GLenum blendEquationRGB = GL_FUNC_ADD; + GLenum blendEquationA = GL_FUNC_ADD; + GLenum blendFunctionSrcRGB = GL_ONE; + GLenum blendFunctionSrcA = GL_ONE; + GLenum blendFunctionDstRGB = GL_ZERO; + GLenum blendFunctionDstA = GL_ZERO; + GLboolean colorMask = GL_TRUE; + GLboolean depthMask = GL_TRUE; + GLenum depthFunc = GL_LESS; + } raster; + + struct { + struct StencilFunc { + GLenum func = GL_ALWAYS; + GLint ref = 0; + GLuint mask = ~GLuint(0); + bool operator != (StencilFunc const& rhs) const noexcept { + return func != rhs.func || ref != rhs.ref || mask != rhs.mask; + } + }; + struct StencilOp { + GLenum sfail = GL_KEEP; + GLenum dpfail = GL_KEEP; + GLenum dppass = GL_KEEP; + bool operator != (StencilOp const& rhs) const noexcept { + return sfail != rhs.sfail || dpfail != rhs.dpfail || dppass != rhs.dppass; + } + }; + struct { + StencilFunc func; + StencilOp op; + GLuint stencilMask = ~GLuint(0); + } front, back; + } stencil; + + struct PolygonOffset { + GLfloat factor = 0; + GLfloat units = 0; + bool operator != (PolygonOffset const& rhs) const noexcept { + return factor != rhs.factor || units != rhs.units; + } + } polygonOffset; + + struct { + utils::bitset32 caps; + } enables; + + struct { + struct { + struct { + GLuint name = 0; + GLintptr offset = 0; + GLsizeiptr size = 0; + } buffers[MAX_BUFFER_BINDINGS]; + } targets[3]; // there are only 3 indexed buffer targets + GLuint genericBinding[7] = {}; + } buffers; + + struct { + GLuint active = 0; // zero-based + struct { + GLuint sampler = 0; + GLuint target = 0; + GLuint id = 0; + } units[MAX_TEXTURE_UNIT_COUNT]; + } textures; + + struct { + GLint row_length = 0; + GLint alignment = 4; + } unpack; + + struct { + GLint alignment = 4; + } pack; + + struct { + vec4gli scissor { 0 }; + vec4gli viewport { 0 }; + vec2glf depthRange { 0.0f, 1.0f }; + } window; + uint8_t age = 0; + } state; + +private: + OpenGLContext const& mContext; + TimerQueryFactoryInterface* mTimerQueryFactory = nullptr; + std::vector> mDestroyWithNormalContext; + RenderPrimitive mDefaultVAO; + std::optional mDefaultFbo[2]; // 0:Unprotected, 1:Protected + mutable tsl::robin_map mSamplerMap; + + void bindFramebufferResolved(GLenum target, GLuint buffer) noexcept; + + void setDefaultState() noexcept; + + template + static inline void update_state(T& state, T const& expected, F functor, bool force = false) noexcept { + if (UTILS_UNLIKELY(force || state != expected)) { + state = expected; + functor(); + } + } +}; + +// ------------------------------------------------------------------------------------------------ + +constexpr size_t OpenGLState::getIndexForCap(GLenum cap) noexcept { //NOLINT + size_t index = 0; + switch (cap) { + case GL_BLEND: index = 0; break; + case GL_CULL_FACE: index = 1; break; + case GL_SCISSOR_TEST: index = 2; break; + case GL_DEPTH_TEST: index = 3; break; + case GL_STENCIL_TEST: index = 4; break; + case GL_DITHER: index = 5; break; + case GL_SAMPLE_ALPHA_TO_COVERAGE: index = 6; break; + case GL_SAMPLE_COVERAGE: index = 7; break; + case GL_POLYGON_OFFSET_FILL: index = 8; break; +#ifdef GL_ARB_seamless_cube_map + case GL_TEXTURE_CUBE_MAP_SEAMLESS: index = 9; break; +#endif +#ifdef BACKEND_OPENGL_VERSION_GL + case GL_PROGRAM_POINT_SIZE: index = 10; break; +#endif + case GL_DEPTH_CLAMP: index = 11; break; + default: break; + } + assert_invariant(index < state.enables.caps.size()); + return index; +} + +constexpr size_t OpenGLState::getIndexForBufferTarget(GLenum target) noexcept { + size_t index = 0; + switch (target) { + // The indexed buffers MUST be first in this list (those usable with bindBufferRange) +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 + case GL_UNIFORM_BUFFER: index = 0; break; + case GL_TRANSFORM_FEEDBACK_BUFFER: index = 1; break; +#if defined(BACKEND_OPENGL_LEVEL_GLES31) + case GL_SHADER_STORAGE_BUFFER: index = 2; break; +#endif +#endif + case GL_ARRAY_BUFFER: index = 3; break; + case GL_ELEMENT_ARRAY_BUFFER: index = 4; break; +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 + case GL_PIXEL_PACK_BUFFER: index = 5; break; + case GL_PIXEL_UNPACK_BUFFER: index = 6; break; +#endif + default: break; + } + assert_invariant(index < sizeof(state.buffers.genericBinding)/sizeof(state.buffers.genericBinding[0])); // NOLINT(misc-redundant-expression) + return index; +} + +// ------------------------------------------------------------------------------------------------ + +void OpenGLState::activeTexture(GLuint unit) noexcept { + assert_invariant(unit < MAX_TEXTURE_UNIT_COUNT); + update_state(state.textures.active, unit, [&]() { + glActiveTexture(GL_TEXTURE0 + unit); + }); +} + +void OpenGLState::bindSampler(GLuint unit, GLuint sampler) noexcept { + assert_invariant(unit < MAX_TEXTURE_UNIT_COUNT); + assert_invariant(mContext.getFeatureLevel() >= FeatureLevel::FEATURE_LEVEL_1); +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 + update_state(state.textures.units[unit].sampler, sampler, [&]() { + glBindSampler(unit, sampler); + }); +#endif +} + +void OpenGLState::setScissor(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept { + vec4gli const scissor(left, bottom, width, height); + update_state(state.window.scissor, scissor, [&]() { + glScissor(left, bottom, width, height); + }); +} + +void OpenGLState::viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept { + vec4gli const viewport(left, bottom, width, height); + update_state(state.window.viewport, viewport, [&]() { + glViewport(left, bottom, width, height); + }); +} + +void OpenGLState::depthRange(GLclampf near, GLclampf far) noexcept { + vec2glf const depthRange(near, far); + update_state(state.window.depthRange, depthRange, [&]() { + glDepthRangef(near, far); + }); +} + +void OpenGLState::bindVertexArray(RenderPrimitive const* p) noexcept { + RenderPrimitive* vao = p ? const_cast(p) : &mDefaultVAO; + update_state(state.vao.p, vao, [&]() { + + // See if we need to create a name for this VAO on the fly, this would happen if: + // - we're not the default VAO, because its name is always 0 + // - our name is 0, this could happen if this VAO was created in the "other" context + // - the nameVersion is out of date *and* we're on the protected context, in this case: + // - the name must be stale from a previous use of this context because we always + // destroy the protected context when we're done with it. + bool const recreateVaoName = vao != &mDefaultVAO && + ((vao->vao[contextIndex] == 0) || + (vao->nameVersion != state.age && contextIndex == 1)); + if (UTILS_UNLIKELY(recreateVaoName)) { + vao->nameVersion = state.age; + procs.genVertexArrays(1, &vao->vao[contextIndex]); + } + + procs.bindVertexArray(vao->vao[contextIndex]); + // update GL_ELEMENT_ARRAY_BUFFER, which is updated by glBindVertexArray + size_t const targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER); + state.buffers.genericBinding[targetIndex] = vao->elementArray; + if (UTILS_UNLIKELY(bugs.vao_doesnt_store_element_array_buffer_binding || recreateVaoName)) { + // This shouldn't be needed, but it looks like some drivers don't do the implicit + // glBindBuffer(). + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao->elementArray); + } + }); +} + +void OpenGLState::bindBufferRange(GLenum target, GLuint index, GLuint buffer, + GLintptr offset, GLsizeiptr size) noexcept { + assert_invariant(mContext.getFeatureLevel() >= FeatureLevel::FEATURE_LEVEL_1); + +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 +# ifdef BACKEND_OPENGL_LEVEL_GLES31 + assert_invariant(false + || target == GL_UNIFORM_BUFFER + || target == GL_TRANSFORM_FEEDBACK_BUFFER + || target == GL_SHADER_STORAGE_BUFFER); +# else + assert_invariant(false + || target == GL_UNIFORM_BUFFER + || target == GL_TRANSFORM_FEEDBACK_BUFFER); +# endif + size_t const targetIndex = getIndexForBufferTarget(target); + // this ALSO sets the generic binding + assert_invariant(targetIndex < sizeof(state.buffers.targets) / sizeof(*state.buffers.targets)); + if ( state.buffers.targets[targetIndex].buffers[index].name != buffer + || state.buffers.targets[targetIndex].buffers[index].offset != offset + || state.buffers.targets[targetIndex].buffers[index].size != size) { + state.buffers.targets[targetIndex].buffers[index].name = buffer; + state.buffers.targets[targetIndex].buffers[index].offset = offset; + state.buffers.targets[targetIndex].buffers[index].size = size; + state.buffers.genericBinding[targetIndex] = buffer; + glBindBufferRange(target, index, buffer, offset, size); + } +#endif +} + +void OpenGLState::bindTexture(GLuint unit, GLuint target, GLuint texId, bool external) noexcept { + // another texture is bound to the same unit with a different target, + // unbind the texture from the current target + update_state(state.textures.units[unit].target, target, [&]() { + activeTexture(unit); + glBindTexture(state.textures.units[unit].target, 0); + }); + update_state(state.textures.units[unit].id, texId, [&]() { + activeTexture(unit); + glBindTexture(target, texId); + }, external); +} + +void OpenGLState::useProgram(GLuint program) noexcept { + update_state(state.program.use, program, [&]() { + glUseProgram(program); + }); +} + +void OpenGLState::enableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept { + assert_invariant(rp); + assert_invariant(index < rp->vertexAttribArray.size()); + bool const force = rp->stateVersion != state.age; + if (UTILS_UNLIKELY(force || !rp->vertexAttribArray[index])) { + rp->vertexAttribArray.set(index); + glEnableVertexAttribArray(index); + } +} + +void OpenGLState::disableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept { + assert_invariant(rp); + assert_invariant(index < rp->vertexAttribArray.size()); + bool const force = rp->stateVersion != state.age; + if (UTILS_UNLIKELY(force || rp->vertexAttribArray[index])) { + rp->vertexAttribArray.unset(index); + glDisableVertexAttribArray(index); + } +} + +void OpenGLState::enable(GLenum cap) noexcept { + size_t const index = getIndexForCap(cap); + if (UTILS_UNLIKELY(!state.enables.caps[index])) { + state.enables.caps.set(index); + glEnable(cap); + } +} + +void OpenGLState::disable(GLenum cap) noexcept { + size_t const index = getIndexForCap(cap); + if (UTILS_UNLIKELY(state.enables.caps[index])) { + state.enables.caps.unset(index); + glDisable(cap); + } +} + +void OpenGLState::frontFace(GLenum mode) noexcept { + update_state(state.raster.frontFace, mode, [&]() { + glFrontFace(mode); + }); +} + +void OpenGLState::cullFace(GLenum mode) noexcept { + update_state(state.raster.cullFace, mode, [&]() { + glCullFace(mode); + }); +} + +void OpenGLState::blendEquation(GLenum modeRGB, GLenum modeA) noexcept { + if (UTILS_UNLIKELY( + state.raster.blendEquationRGB != modeRGB || state.raster.blendEquationA != modeA)) { + state.raster.blendEquationRGB = modeRGB; + state.raster.blendEquationA = modeA; + glBlendEquationSeparate(modeRGB, modeA); + } +} + +void OpenGLState::blendFunction(GLenum srcRGB, GLenum srcA, GLenum dstRGB, GLenum dstA) noexcept { + if (UTILS_UNLIKELY( + state.raster.blendFunctionSrcRGB != srcRGB || + state.raster.blendFunctionSrcA != srcA || + state.raster.blendFunctionDstRGB != dstRGB || + state.raster.blendFunctionDstA != dstA)) { + state.raster.blendFunctionSrcRGB = srcRGB; + state.raster.blendFunctionSrcA = srcA; + state.raster.blendFunctionDstRGB = dstRGB; + state.raster.blendFunctionDstA = dstA; + glBlendFuncSeparate(srcRGB, dstRGB, srcA, dstA); + } +} + +void OpenGLState::colorMask(GLboolean flag) noexcept { + update_state(state.raster.colorMask, flag, [&]() { + glColorMask(flag, flag, flag, flag); + }); +} +void OpenGLState::depthMask(GLboolean flag) noexcept { + update_state(state.raster.depthMask, flag, [&]() { + glDepthMask(flag); + }); +} + +void OpenGLState::depthFunc(GLenum func) noexcept { + update_state(state.raster.depthFunc, func, [&]() { + glDepthFunc(func); + }); +} + +void OpenGLState::stencilFuncSeparate(GLenum funcFront, GLint refFront, GLuint maskFront, + GLenum funcBack, GLint refBack, GLuint maskBack) noexcept { + update_state(state.stencil.front.func, {funcFront, refFront, maskFront}, [&]() { + glStencilFuncSeparate(GL_FRONT, funcFront, refFront, maskFront); + }); + update_state(state.stencil.back.func, {funcBack, refBack, maskBack}, [&]() { + glStencilFuncSeparate(GL_BACK, funcBack, refBack, maskBack); + }); +} + +void OpenGLState::stencilOpSeparate(GLenum sfailFront, GLenum dpfailFront, GLenum dppassFront, + GLenum sfailBack, GLenum dpfailBack, GLenum dppassBack) noexcept { + update_state(state.stencil.front.op, {sfailFront, dpfailFront, dppassFront}, [&]() { + glStencilOpSeparate(GL_FRONT, sfailFront, dpfailFront, dppassFront); + }); + update_state(state.stencil.back.op, {sfailBack, dpfailBack, dppassBack}, [&]() { + glStencilOpSeparate(GL_BACK, sfailBack, dpfailBack, dppassBack); + }); +} + +void OpenGLState::stencilMaskSeparate(GLuint maskFront, GLuint maskBack) noexcept { + update_state(state.stencil.front.stencilMask, maskFront, [&]() { + glStencilMaskSeparate(GL_FRONT, maskFront); + }); + update_state(state.stencil.back.stencilMask, maskBack, [&]() { + glStencilMaskSeparate(GL_BACK, maskBack); + }); +} + +void OpenGLState::polygonOffset(GLfloat factor, GLfloat units) noexcept { + update_state(state.polygonOffset, { factor, units }, [&]() { + if (factor != 0 || units != 0) { + glPolygonOffset(factor, units); + enable(GL_POLYGON_OFFSET_FILL); + } else { + disable(GL_POLYGON_OFFSET_FILL); + } + }); +} + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_OPENGL_OPENGLSTATE_H diff --git a/filament/backend/src/opengl/ShaderCompilerService.cpp b/filament/backend/src/opengl/ShaderCompilerService.cpp index bc22e4ad67..bf140a3c95 100644 --- a/filament/backend/src/opengl/ShaderCompilerService.cpp +++ b/filament/backend/src/opengl/ShaderCompilerService.cpp @@ -237,7 +237,7 @@ void ShaderCompilerService::init() noexcept { uint32_t poolSize = 1; JobSystem::Priority priority = JobSystem::Priority::DISPLAY; - auto const& renderer = mDriver.getContext().state.renderer; + auto const& renderer = mDriver.getContext().renderer; // Some drivers support parallel shader compilation well, so we use N // threads, we can use lower priority threads here because urgent compilations // will most likely happen on the main gl thread. Using too many thread can