From bedbcbb7eb0154c389f7d9961fd8e22ba78523eb Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Fri, 27 Mar 2026 13:53:53 -0700 Subject: [PATCH] OpenGL: Factor per-thread state out of OpenGLContext (#9830) The OpenGLContext class in the OpenGL backend currently conflates two distinct parts: immutable configuration (extensions, bugs, capabilities) and a mutable GL state cache. This single-object design is incompatible with a multi-threaded rendering architecture, where each thread requires a dedicated GL context and, critically, its own independent state machine/cache. This refactoring implements the proposed architecture by: 1. Introducing a new OpenGLState class to exclusively manage the mutable GL state cache (texture bindings, buffer bindings, etc.) with explicit per-thread ownership (e.g., mBackendState and mWorkerState). 2. Ensuring OpenGLContext retains only the shared, immutable configuration. 3. Preserving the existing gl.XXX usage pattern by implementing inline accessors in OpenGLState that forward read-only queries (e.g., gl.ext.XXX, gl.isES2()) to the back-referenced OpenGLContext. 4. Updating common functions shared between backend and worker threads to explicitly accept an OpenGLState& parameter. This change enables a multi-threaded approach by guaranteeing each thread operates on its own dedicated GL state cache, while reusing the shared, immutable context configuration. BUGS=[487347473] --- filament/backend/CMakeLists.txt | 2 + .../backend/src/opengl/GLDescriptorSet.cpp | 14 +- filament/backend/src/opengl/GLDescriptorSet.h | 12 +- .../src/opengl/GLMemoryMappedBuffer.cpp | 20 +- .../backend/src/opengl/GLMemoryMappedBuffer.h | 8 +- filament/backend/src/opengl/OpenGLContext.cpp | 567 +--------------- filament/backend/src/opengl/OpenGLContext.h | 568 +--------------- filament/backend/src/opengl/OpenGLDriver.cpp | 255 ++++---- filament/backend/src/opengl/OpenGLDriver.h | 46 +- filament/backend/src/opengl/OpenGLProgram.cpp | 10 +- filament/backend/src/opengl/OpenGLProgram.h | 8 +- filament/backend/src/opengl/OpenGLState.cpp | 559 ++++++++++++++++ filament/backend/src/opengl/OpenGLState.h | 615 ++++++++++++++++++ .../src/opengl/ShaderCompilerService.cpp | 2 +- 14 files changed, 1419 insertions(+), 1267 deletions(-) create mode 100644 filament/backend/src/opengl/OpenGLState.cpp create mode 100644 filament/backend/src/opengl/OpenGLState.h 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