diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 550f569f02..75416a3d4d 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -68,6 +68,8 @@ set(PRIVATE_HDRS if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3 AND NOT FILAMENT_USE_SWIFTSHADER) list(APPEND SRCS include/backend/platforms/OpenGLPlatform.h + src/opengl/CallbackManager.h + src/opengl/CallbackManager.cpp src/opengl/gl_headers.cpp src/opengl/gl_headers.h src/opengl/GLUtils.cpp diff --git a/filament/backend/src/CompilerThreadPool.cpp b/filament/backend/src/CompilerThreadPool.cpp index 008b353aaa..eeb116db3b 100644 --- a/filament/backend/src/CompilerThreadPool.cpp +++ b/filament/backend/src/CompilerThreadPool.cpp @@ -16,6 +16,8 @@ #include "CompilerThreadPool.h" +#include + #include namespace filament::backend { @@ -38,6 +40,7 @@ void CompilerThreadPool::init(uint32_t threadCount, JobSystem::Priority priority for (size_t i = 0; i < threadCount; i++) { mCompilerThreads.emplace_back([this, priority, setup]() { + SYSTRACE_CONTEXT(); // give the thread a name JobSystem::setThreadName("CompilerThreadPool"); // run at a slightly lower priority than other filament threads @@ -53,7 +56,11 @@ void CompilerThreadPool::init(uint32_t threadCount, JobSystem::Priority priority (!std::all_of( std::begin(mQueues), std::end(mQueues), [](auto&& q) { return q.empty(); })); }); - if (!mExitRequested) { + + SYSTRACE_VALUE32("CompilerThreadPool Jobs", + mQueues[0].size() + mQueues[1].size()); + + if (UTILS_LIKELY(!mExitRequested)) { Job job; // use the first queue that's not empty auto& queue = [this]() -> auto& { diff --git a/filament/backend/src/DriverBase.h b/filament/backend/src/DriverBase.h index 3e7f2647d2..abf6890116 100644 --- a/filament/backend/src/DriverBase.h +++ b/filament/backend/src/DriverBase.h @@ -165,13 +165,6 @@ public: void purge() noexcept final; - // -------------------------------------------------------------------------------------------- - // Privates - // -------------------------------------------------------------------------------------------- - -protected: - class CallbackDataDetails; - // Helpers... struct CallbackData { CallbackData(CallbackData const &) = delete; @@ -202,6 +195,13 @@ protected: void scheduleCallback(CallbackHandler* handler, void* user, CallbackHandler::Callback callback); + // -------------------------------------------------------------------------------------------- + // Privates + // -------------------------------------------------------------------------------------------- + +protected: + class CallbackDataDetails; + inline void scheduleDestroy(BufferDescriptor&& buffer) noexcept { if (buffer.hasCallback()) { scheduleDestroySlow(std::move(buffer)); diff --git a/filament/backend/src/opengl/CallbackManager.cpp b/filament/backend/src/opengl/CallbackManager.cpp new file mode 100644 index 0000000000..8d85a9f488 --- /dev/null +++ b/filament/backend/src/opengl/CallbackManager.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 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 "CallbackManager.h" + +#include "DriverBase.h" + +namespace filament::backend { + +CallbackManager::CallbackManager(DriverBase& driver) noexcept + : mDriver(driver), mCallbacks(1) { +} + +CallbackManager::~CallbackManager() noexcept = default; + +void CallbackManager::terminate() noexcept { + for (auto&& item: mCallbacks) { + if (item.func) { + mDriver.scheduleCallback( + item.handler, item.user, item.func); + } + } +} + +CallbackManager::Handle CallbackManager::get() const noexcept { + Container::const_iterator const curr = getCurrent(); + curr->count.fetch_add(1); + return curr; +} + +void CallbackManager::put(Handle& curr) noexcept { + if (curr->count.fetch_sub(1) == 1) { + if (curr->func) { + mDriver.scheduleCallback( + curr->handler, curr->user, curr->func); + destroySlot(curr); + } + } + curr = {}; +} + +void CallbackManager::setCallback( + CallbackHandler* handler, CallbackHandler::Callback func, void* user) { + assert_invariant(func); + Container::iterator const curr = allocateNewSlot(); + curr->handler = handler; + curr->func = func; + curr->user = user; + if (curr->count == 0) { + mDriver.scheduleCallback( + curr->handler, curr->user, curr->func); + destroySlot(curr); + } +} + +} // namespace filament::backend diff --git a/filament/backend/src/opengl/CallbackManager.h b/filament/backend/src/opengl/CallbackManager.h new file mode 100644 index 0000000000..5349f20126 --- /dev/null +++ b/filament/backend/src/opengl/CallbackManager.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2023 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_CALLBACKMANAGER_H +#define TNT_FILAMENT_BACKEND_OPENGL_CALLBACKMANAGER_H + +#include + +#include + +#include +#include +#include + +namespace filament::backend { + +class DriverBase; +class CallbackHandler; + +/* + * CallbackManager schedules user callbacks once all previous conditions are met. + * A "Condition" is created by calling "get" and is met by calling "put". These + * are typically called from different threads. + * The callback is specified with "setCallback", which atomically creates a new set of + * conditions to be met. + */ +class CallbackManager { + struct Callback { + mutable std::atomic_int count{}; + CallbackHandler* handler = nullptr; + CallbackHandler::Callback func = {}; + void* user = nullptr; + }; + + using Container = std::list; + +public: + using Handle = Container::const_iterator; + + explicit CallbackManager(DriverBase& driver) noexcept; + + ~CallbackManager() noexcept; + + // Calls all the pending callbacks regardless of remaining conditions to be met. This is to + // avoid leaking resources for instance. It also doesn't matter if the conditions are met + // because we're shutting down. + void terminate() noexcept; + + // creates a condition and get a handle for it + Handle get() const noexcept; + + // Announces the specified condition is met. If a callback was specified and all conditions + // prior to setting the callback are met, the callback is scheduled. + void put(Handle& curr) noexcept; + + // Sets a callback to be called when all previously created (get) conditions are met (put). + // If there were no conditions created, or they're all already met, the callback is scheduled + // immediately. + void setCallback(CallbackHandler* handler, CallbackHandler::Callback func, void* user); + +private: + Container::const_iterator getCurrent() const noexcept { + std::lock_guard const lock(mLock); + return --mCallbacks.end(); + } + + Container::iterator allocateNewSlot() noexcept { + std::lock_guard const lock(mLock); + auto curr = --mCallbacks.end(); + mCallbacks.emplace_back(); + return curr; + } + void destroySlot(Container::const_iterator curr) noexcept { + std::lock_guard const lock(mLock); + mCallbacks.erase(curr); + } + + DriverBase& mDriver; + mutable utils::Mutex mLock; + Container mCallbacks; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_OPENGL_CALLBACKMANAGER_H diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index 47cc50edfc..3d4eef3076 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -2593,7 +2593,7 @@ bool OpenGLDriver::getTimerQueryValue(Handle tqh, uint64_t* elapse void OpenGLDriver::compilePrograms(CompilerPriorityQueue priority, CallbackHandler* handler, CallbackHandler::Callback callback, void* user) { if (callback) { - getShaderCompilerService().notifyWhenAllProgramsAreReady(priority, handler, callback, user); + getShaderCompilerService().notifyWhenAllProgramsAreReady(handler, callback, user); } } diff --git a/filament/backend/src/opengl/ShaderCompilerService.cpp b/filament/backend/src/opengl/ShaderCompilerService.cpp index 3dfdea5f47..dcbfb0f9f9 100644 --- a/filament/backend/src/opengl/ShaderCompilerService.cpp +++ b/filament/backend/src/opengl/ShaderCompilerService.cpp @@ -64,17 +64,17 @@ static inline std::string to_string(float f) noexcept { // ------------------------------------------------------------------------------------------------ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken { - struct ProgramBinary { - GLenum format{}; + struct ProgramData { GLuint program{}; std::array shaders{}; - std::vector blob; }; - ~OpenGLProgramToken(); + ~OpenGLProgramToken() override; + OpenGLProgramToken(ShaderCompilerService& compiler, utils::CString const& name) noexcept : compiler(compiler), name(name) { } + ShaderCompilerService& compiler; utils::CString const& name; utils::FixedCapacityVector> attributes; @@ -86,22 +86,21 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken { } gl; // 12 bytes - // Sets the programBinary, typically from the compiler thread, and signal the main thread. + // Sets the programData, typically from the compiler thread, and signal the main thread. // This is similar to std::promise::set_value. - void set(ProgramBinary programBinary) noexcept { - using std::swap; + void set(ProgramData const& data) noexcept { std::unique_lock const l(lock); - swap(binary, programBinary); + programData = data; signaled = true; cond.notify_one(); } // Get the programBinary, wait if necessary. // This is similar to std::future::get - ProgramBinary const& get() const noexcept { + ProgramData const& get() const noexcept { std::unique_lock l(lock); cond.wait(l, [this](){ return signaled; }); - return binary; + return programData; } // Checks if the programBinary is ready. @@ -112,10 +111,11 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken { return cond.wait_for(l, 0s, [this](){ return signaled; }); } + CallbackManager::Handle handle{}; BlobCacheKey key; mutable utils::Mutex lock; mutable utils::Condition cond; - ProgramBinary binary; + ProgramData programData; bool signaled = false; bool canceled = false; // not part of the signaling @@ -135,6 +135,7 @@ void* ShaderCompilerService::getUserData(const program_token_t& token) noexcept ShaderCompilerService::ShaderCompilerService(OpenGLDriver& driver) : mDriver(driver), + mCallbackManager(driver), KHR_parallel_shader_compile(driver.getContext().ext.KHR_parallel_shader_compile) { } @@ -171,31 +172,25 @@ void ShaderCompilerService::init() noexcept { mShaderCompilerThreadCount = poolSize; mCompilerThreadPool.init(mShaderCompilerThreadCount, priority, - [platform = &mDriver.mPlatform, sharedContext = mUseSharedContext]() { + [platform = &mDriver.mPlatform]() { // create a gl context current to this thread - platform->createContext(sharedContext); + platform->createContext(true); }); } } } void ShaderCompilerService::terminate() noexcept { - // We could have some pending callbacks here, we need to execute them. - // This is equivalent to calling cancelTickOp() on all active tokens. - for (auto&& op: mRunAtNextTickOps) { - auto const& [priority, token, job] = op; - if (!token && job.callback) { - // This is a little fragile here. We know by construction that jobs that have a - // null token are the ones that dispatch the user callbacks. - mDriver.scheduleCallback(job.handler, job.user, job.callback); - } - } - mRunAtNextTickOps.clear(); - // Finally stop the thread pool immediately. Pending jobs will be discarded. We guarantee by // construction that nobody is waiting on a token (because waiting is only done on the main // backend thread, and if we're here, we're on the backend main thread). mCompilerThreadPool.terminate(); + + mRunAtNextTickOps.clear(); + + // We could have some pending callbacks here, we need to execute them. + // This is equivalent to calling cancelTickOp() on all active tokens. + mCallbackManager.terminate(); } ShaderCompilerService::program_token_t ShaderCompilerService::createProgram( @@ -203,132 +198,104 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram( auto& gl = mDriver.getContext(); auto token = std::make_shared(*this, name); - if (UTILS_UNLIKELY(gl.isES2())) { token->attributes = std::move(program.getAttributes()); } token->gl.program = OpenGLBlobCache::retrieve(&token->key, mDriver.mPlatform, program); - if (!token->gl.program) { - CompilerPriorityQueue const priorityQueue = program.getPriorityQueue(); - if (mShaderCompilerThreadCount) { - // queue a compile job - mCompilerThreadPool.queue(priorityQueue, token, - [this, &gl, program = std::move(program), token]() mutable { + if (token->gl.program) { + return token; + } - // compile the shaders - std::array shaders{}; - std::array shaderSourceCode; - compileShaders(gl, - std::move(program.getShadersSource()), - program.getSpecializationConstants(), - shaders, - shaderSourceCode); + token->handle = mCallbackManager.get(); - // link the program - GLuint const glProgram = linkProgram(gl, shaders, token->attributes); + CompilerPriorityQueue const priorityQueue = program.getPriorityQueue(); + if (mShaderCompilerThreadCount) { + // queue a compile job + mCompilerThreadPool.queue(priorityQueue, token, + [this, &gl, program = std::move(program), token]() mutable { + // compile the shaders + std::array shaders{}; + std::array shaderSourceCode; + compileShaders(gl, + std::move(program.getShadersSource()), + program.getSpecializationConstants(), + shaders, + shaderSourceCode); - OpenGLProgramToken::ProgramBinary binary; - binary.shaders = shaders; + // link the program + GLuint const glProgram = linkProgram(gl, shaders, token->attributes); - if (UTILS_LIKELY(mUseSharedContext)) { - // We need to query the link status here to guarantee that the - // program is compiled and linked now (we don't want this to be - // deferred to later). We don't care about the result at this point. - GLint status; - glGetProgramiv(glProgram, GL_LINK_STATUS, &status); - binary.program = glProgram; - if (token->key) { - // Attempt to cache. This calls glGetProgramBinary. - OpenGLBlobCache::insert(mDriver.mPlatform, token->key, glProgram); - } - } -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - else { - // retrieve the program binary - GLsizei programBinarySize = 0; - glGetProgramiv(glProgram, GL_PROGRAM_BINARY_LENGTH, &programBinarySize); - assert_invariant(programBinarySize); - if (programBinarySize) { - binary.blob.resize(programBinarySize); - glGetProgramBinary(glProgram, programBinarySize, - &programBinarySize, &binary.format, binary.blob.data()); - } - // and we can destroy the program - glDeleteProgram(glProgram); - if (token->key) { - // attempt to cache - OpenGLBlobCache::insert(mDriver.mPlatform, token->key, - binary.format, - binary.blob.data(), GLsizei(binary.blob.size())); - } - } -#endif - // we don't need to check for success here, it'll be done on the - // main thread side. - token->set(std::move(binary)); - }); - } else - { - // this cannot fail because we check compilation status after linking the program - // shaders[] is filled with id of shader stages present. - compileShaders(gl, - std::move(program.getShadersSource()), - program.getSpecializationConstants(), - token->gl.shaders, - token->shaderSourceCode); + OpenGLProgramToken::ProgramData programData; + programData.shaders = shaders; - } + // We need to query the link status here to guarantee that the + // program is compiled and linked now (we don't want this to be + // deferred to later). We don't care about the result at this point. + GLint status; + glGetProgramiv(glProgram, GL_LINK_STATUS, &status); + programData.program = glProgram; + + token->gl.program = programData.program; + + // we don't need to check for success here, it'll be done on the + // main thread side. + token->set(programData); + + mCallbackManager.put(token->handle); + + // caching must be the last thing we do + if (token->key) { + // Attempt to cache. This calls glGetProgramBinary. + OpenGLBlobCache::insert(mDriver.mPlatform, token->key, glProgram); + } + }); + + } else { + // this cannot fail because we check compilation status after linking the program + // shaders[] is filled with id of shader stages present. + compileShaders(gl, + std::move(program.getShadersSource()), + program.getSpecializationConstants(), + token->gl.shaders, + token->shaderSourceCode); runAtNextTick(priorityQueue, token, [this, token](Job const&) { - if (mShaderCompilerThreadCount) { - if (!token->gl.program) { - // TODO: see if we could completely eliminate this callback here - // and instead just rely on token->gl.program being atomically - // set by the compiler thread. - // we're using the compiler thread, check if the program is ready, no-op if not. - if (!token->isReady()) { + if (KHR_parallel_shader_compile) { + // don't attempt to link this program if all shaders are not done compiling + GLint status; + if (token->gl.program) { + glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status); + if (status == GL_FALSE) { return false; } - // program binary is ready, retrieve it without blocking - ShaderCompilerService::getProgramFromCompilerPool( - const_cast(token)); - } - } else { - if (KHR_parallel_shader_compile) { - // don't attempt to link this program if all shaders are not done compiling - GLint status; - if (token->gl.program) { - glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status); - if (status == GL_FALSE) { - return false; - } - } else { - for (auto shader: token->gl.shaders) { - if (shader) { - glGetShaderiv(shader, GL_COMPLETION_STATUS, &status); - if (status == GL_FALSE) { - return false; - } + } else { + for (auto shader: token->gl.shaders) { + if (shader) { + glGetShaderiv(shader, GL_COMPLETION_STATUS, &status); + if (status == GL_FALSE) { + return false; } } } } + } - if (!token->gl.program) { - // link the program, this also cannot fail because status is checked later. - token->gl.program = linkProgram(mDriver.getContext(), - token->gl.shaders, token->attributes); - if (KHR_parallel_shader_compile) { - // wait until the link finishes... - return false; - } + if (!token->gl.program) { + // link the program, this also cannot fail because status is checked later. + token->gl.program = linkProgram(mDriver.getContext(), + token->gl.shaders, token->attributes); + if (KHR_parallel_shader_compile) { + // wait until the link finishes... + return false; } } assert_invariant(token->gl.program); - if (token->key && !mShaderCompilerThreadCount) { + mCallbackManager.put(token->handle); + + if (token->key) { // TODO: technically we don't have to cache right now. Is it advantageous to // do this later, maybe depending on CPU usage? // attempt to cache if we don't have a thread pool (otherwise it's done @@ -343,27 +310,6 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram( return token; } -bool ShaderCompilerService::isProgramReady( - const ShaderCompilerService::program_token_t& token) const noexcept { - - assert_invariant(token); - - if (!token->gl.program) { - return false; - } - - if (KHR_parallel_shader_compile) { - GLint status = GL_FALSE; - glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status); - return (bool)status; - } - - // If gl.program is set, this means the program was linked. Some drivers may defer the link - // in which case we might block in getProgram() when we check the program status. - // Unfortunately, this is nothing we can do about that. - return bool(token->gl.program); -} - GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& token) { GLuint const program = initialize(token); assert_invariant(token == nullptr); @@ -395,74 +341,26 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& } void ShaderCompilerService::tick() { - executeTickOps(); + // we don't need to run executeTickOps() if we're using the thread-pool + if (UTILS_UNLIKELY(!mShaderCompilerThreadCount)) { + executeTickOps(); + } } -void ShaderCompilerService::notifyWhenAllProgramsAreReady(CompilerPriorityQueue priority, +void ShaderCompilerService::notifyWhenAllProgramsAreReady( CallbackHandler* handler, CallbackHandler::Callback callback, void* user) { - - if (KHR_parallel_shader_compile || mShaderCompilerThreadCount) { - // list all programs up to this point, both low and high priority - - using TokenVector = utils::FixedCapacityVector< - program_token_t, std::allocator, false>; - TokenVector tokens{ TokenVector::with_capacity(mRunAtNextTickOps.size()) }; - - for (auto& [itemPriority, token, job] : mRunAtNextTickOps) { - if (token && job.fn && itemPriority == priority) { - tokens.push_back(token); - } - } - - runAtNextTick(priority, nullptr, { - [this, tokens = std::move(tokens)](Job const& job) { - for (auto const& token : tokens) { - assert_invariant(token); - if (!isProgramReady(token)) { - // one of the program is not ready, try next time - return false; - } - } - if (job.callback) { - // all programs are ready, we can call the callbacks - mDriver.scheduleCallback(job.handler, job.user, job.callback); - } - // and we're done - return true; - }, handler, user, callback }); - - return; + if (callback) { + mCallbackManager.setCallback(handler, callback, user); } - - // we don't have KHR_parallel_shader_compile - - runAtNextTick(priority, nullptr, {[this](Job const& job) { - mDriver.scheduleCallback(job.handler, job.user, job.callback); - return true; - }, handler, user, callback }); - - // TODO: we could spread the compiles over several frames, the tick() below then is not - // needed here. We keep it for now as to not change the current behavior too much. - // this will block until all programs are linked - tick(); } // ------------------------------------------------------------------------------------------------ void ShaderCompilerService::getProgramFromCompilerPool(program_token_t& token) noexcept { - OpenGLProgramToken::ProgramBinary const& binary{ token->get() }; + OpenGLProgramToken::ProgramData const& programData{ token->get() }; if (!token->canceled) { - token->gl.shaders = binary.shaders; - if (UTILS_LIKELY(mUseSharedContext)) { - token->gl.program = binary.program; - } -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - else { - token->gl.program = glCreateProgram(); - glProgramBinary(token->gl.program, binary.format, - binary.blob.data(), GLsizei(binary.blob.size())); - } -#endif + token->gl.shaders = programData.shaders; + token->gl.program = programData.program; } } @@ -489,8 +387,17 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept { // we force the program link -- which might stall, either here or below in // checkProgramStatus(), but we don't have a choice, we need to use the program now. token->compiler.cancelTickOp(token); + token->gl.program = linkProgram(mDriver.getContext(), token->gl.shaders, token->attributes); + + assert_invariant(token->gl.program); + + mCallbackManager.put(token->handle); + + if (token->key) { + OpenGLBlobCache::insert(mDriver.mPlatform, token->key, token->gl.program); + } } else { // if we don't have a program yet, block until we get it. tick(); diff --git a/filament/backend/src/opengl/ShaderCompilerService.h b/filament/backend/src/opengl/ShaderCompilerService.h index 668d4e31f2..03043ca5ec 100644 --- a/filament/backend/src/opengl/ShaderCompilerService.h +++ b/filament/backend/src/opengl/ShaderCompilerService.h @@ -19,6 +19,7 @@ #include "gl_headers.h" +#include "CallbackManager.h" #include "CompilerThreadPool.h" #include @@ -71,10 +72,6 @@ public: // creates a program (compile + link) asynchronously if supported program_token_t createProgram(utils::CString const& name, Program&& program); - // Returns true if the program is linked (successfully or not). Guarantees that - // getProgram() won't block. Does not block. - bool isProgramReady(const program_token_t& token) const noexcept; - // Return the GL program, blocks if necessary. The Token is destroyed and becomes invalid. GLuint getProgram(program_token_t& token); @@ -91,20 +88,17 @@ public: static void* getUserData(const program_token_t& token) noexcept; // call the callback when all active programs are ready - void notifyWhenAllProgramsAreReady(CompilerPriorityQueue priority, + void notifyWhenAllProgramsAreReady( CallbackHandler* handler, CallbackHandler::Callback callback, void* user); private: OpenGLDriver& mDriver; + CallbackManager mCallbackManager; CompilerThreadPool mCompilerThreadPool; const bool KHR_parallel_shader_compile; uint32_t mShaderCompilerThreadCount = 0u; - // For now, we assume shared contexts are supported everywhere. If they are not, - // we don't use the shader compiler pool. However, the code supports it. - static constexpr bool mUseSharedContext = true; - GLuint initialize(ShaderCompilerService::program_token_t& token) noexcept; static void getProgramFromCompilerPool(program_token_t& token) noexcept;