From c0389ac54c3b89cebaf3e6d9c221395128d3dbdf Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Tue, 15 Aug 2023 16:24:13 -0700 Subject: [PATCH] rework ShaderCompilerService to improve performance - remove support for non-shared contextes parallel compilation. this wasn't used. we can always revive it later if we need to. - rework how callbacks work so that we don't have to use a work list executed at each tick() in the shared context case (common case). this improves performance significantly on low-end devices, by not having to go through the list to check if all programs are compiled, multiple times per frame. The new CallbackManager handles scheduling the callbacks after all previous programs are compiled. --- filament/backend/CMakeLists.txt | 2 + filament/backend/src/CompilerThreadPool.cpp | 9 +- filament/backend/src/DriverBase.h | 14 +- .../backend/src/opengl/CallbackManager.cpp | 69 ++++ filament/backend/src/opengl/CallbackManager.h | 98 ++++++ filament/backend/src/opengl/OpenGLDriver.cpp | 2 +- .../src/opengl/ShaderCompilerService.cpp | 319 +++++++----------- .../src/opengl/ShaderCompilerService.h | 12 +- 8 files changed, 301 insertions(+), 224 deletions(-) create mode 100644 filament/backend/src/opengl/CallbackManager.cpp create mode 100644 filament/backend/src/opengl/CallbackManager.h 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;