diff --git a/README.md b/README.md
index 0ed084ef84b..63f3a490ac7 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
- implementation 'com.google.android.filament:filament-android:1.42.0'
+ implementation 'com.google.android.filament:filament-android:1.42.1'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```
-pod 'Filament', '~> 1.42.0'
+pod 'Filament', '~> 1.42.1'
```
### Snapshots
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 0058fb82e43..cb018b907c3 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -7,6 +7,11 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
+## v1.42.1
+
+- Fix potential `EXC_BAD_ACCESS` with Metal backend: b/297059776
+- `setFrameCompletedCallback` now takes a `backend::CallbackHandler`.
+
## v1.42.0
- engine: add preliminary support for instanced stereoscopic rendering [⚠️ **Recompile materials**]
diff --git a/android/filament-android/src/main/cpp/SwapChain.cpp b/android/filament-android/src/main/cpp/SwapChain.cpp
index 3693803ce18..27e006ae87a 100644
--- a/android/filament-android/src/main/cpp/SwapChain.cpp
+++ b/android/filament-android/src/main/cpp/SwapChain.cpp
@@ -27,11 +27,10 @@ extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_SwapChain_nSetFrameCompletedCallback(JNIEnv* env, jclass,
jlong nativeSwapChain, jobject handler, jobject runnable) {
SwapChain* swapChain = (SwapChain*) nativeSwapChain;
- auto *callback = JniCallback::make(env, handler, runnable);
- swapChain->setFrameCompletedCallback([](void* user) {
- JniCallback* callback = (JniCallback*)user;
+ auto* callback = JniCallback::make(env, handler, runnable);
+ swapChain->setFrameCompletedCallback(nullptr, [callback](SwapChain* swapChain) {
JniCallback::postToJavaAndDestroy(callback);
- }, callback);
+ });
}
extern "C" JNIEXPORT jboolean JNICALL
diff --git a/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java b/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java
index 6d621f02ffb..9c0867fee2d 100644
--- a/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java
+++ b/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java
@@ -137,10 +137,6 @@ public Object getNativeWindow() {
*
*
*
- * The FrameCompletedCallback is guaranteed to be called on the main Filament thread.
- *
- *
- *
* Warning: Only Filament's Metal backend supports frame callbacks. Other backends ignore the
* callback (which will never be called) and proceed normally.
*
diff --git a/android/filament-android/src/main/java/com/google/android/filament/View.java b/android/filament-android/src/main/java/com/google/android/filament/View.java
index ad2abc138bc..1b1d70dac4f 100644
--- a/android/filament-android/src/main/java/com/google/android/filament/View.java
+++ b/android/filament-android/src/main/java/com/google/android/filament/View.java
@@ -27,6 +27,8 @@
import static com.google.android.filament.Asserts.assertFloat4In;
import static com.google.android.filament.Colors.LinearColor;
+import com.google.android.filament.proguard.UsedByNative;
+
/**
* Encompasses all the state needed for rendering a {@link Scene}.
*
@@ -1095,10 +1097,29 @@ public void pick(int x, int y,
nPick(getNativeObject(), x, y, handler, internalCallback);
}
+ @UsedByNative("View.cpp")
private static class InternalOnPickCallback implements Runnable {
+ private final OnPickCallback mUserCallback;
+ private final PickingQueryResult mPickingQueryResult = new PickingQueryResult();
+
+ @UsedByNative("View.cpp")
+ @Entity
+ int mRenderable;
+
+ @UsedByNative("View.cpp")
+ float mDepth;
+
+ @UsedByNative("View.cpp")
+ float mFragCoordsX;
+ @UsedByNative("View.cpp")
+ float mFragCoordsY;
+ @UsedByNative("View.cpp")
+ float mFragCoordsZ;
+
public InternalOnPickCallback(OnPickCallback mUserCallback) {
this.mUserCallback = mUserCallback;
}
+
@Override
public void run() {
mPickingQueryResult.renderable = mRenderable;
@@ -1108,13 +1129,6 @@ public void run() {
mPickingQueryResult.fragCoords[2] = mFragCoordsZ;
mUserCallback.onPick(mPickingQueryResult);
}
- private final OnPickCallback mUserCallback;
- private final PickingQueryResult mPickingQueryResult = new PickingQueryResult();
- @Entity int mRenderable;
- float mDepth;
- float mFragCoordsX;
- float mFragCoordsY;
- float mFragCoordsZ;
}
/**
diff --git a/android/gradle.properties b/android/gradle.properties
index 6962d3e13ee..528d6fba23d 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
-VERSION_NAME=1.42.0
+VERSION_NAME=1.42.1
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
diff --git a/android/samples/sample-image-based-lighting/src/main/java/com/google/android/filament/ibl/MainActivity.kt b/android/samples/sample-image-based-lighting/src/main/java/com/google/android/filament/ibl/MainActivity.kt
index 1554bbdbbe0..723a642307c 100644
--- a/android/samples/sample-image-based-lighting/src/main/java/com/google/android/filament/ibl/MainActivity.kt
+++ b/android/samples/sample-image-based-lighting/src/main/java/com/google/android/filament/ibl/MainActivity.kt
@@ -118,9 +118,10 @@ class MainActivity : Activity() {
}
private fun setupView() {
- val ssaoOptions = view.ambientOcclusionOptions
- ssaoOptions.enabled = true
- view.ambientOcclusionOptions = ssaoOptions
+ // ambient occlusion is the cheapest effect that adds a lot of quality
+ view.ambientOcclusionOptions = view.ambientOcclusionOptions.apply {
+ enabled = true
+ }
// NOTE: Try to disable post-processing (tone-mapping, etc.) to see the difference
// view.isPostProcessingEnabled = false
diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt
index 550f569f02d..7e21bf39199 100644
--- a/filament/backend/CMakeLists.txt
+++ b/filament/backend/CMakeLists.txt
@@ -27,7 +27,6 @@ set(SRCS
src/BackendUtils.cpp
src/BlobCacheKey.cpp
src/Callable.cpp
- src/CallbackHandler.cpp
src/CircularBuffer.cpp
src/CommandBufferQueue.cpp
src/CommandStream.cpp
@@ -68,6 +67,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/include/backend/CallbackHandler.h b/filament/backend/include/backend/CallbackHandler.h
index dee3aaa2515..3ffc707cdd1 100644
--- a/filament/backend/include/backend/CallbackHandler.h
+++ b/filament/backend/include/backend/CallbackHandler.h
@@ -66,7 +66,7 @@ class CallbackHandler {
virtual void post(void* user, Callback callback) = 0;
protected:
- virtual ~CallbackHandler();
+ virtual ~CallbackHandler() = default;
};
} // namespace filament::backend
diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h
index 0e492893fa9..a7ef823941b 100644
--- a/filament/backend/include/backend/DriverEnums.h
+++ b/filament/backend/include/backend/DriverEnums.h
@@ -1126,8 +1126,6 @@ static_assert(sizeof(StencilState) == 12u,
using FrameScheduledCallback = void(*)(PresentCallable callable, void* user);
-using FrameCompletedCallback = void(*)(void* user);
-
enum class Workaround : uint16_t {
// The EASU pass must split because shader compiler flattens early-exit branch
SPLIT_EASU,
diff --git a/filament/backend/include/backend/Program.h b/filament/backend/include/backend/Program.h
index 2a491959e91..72bcf01e420 100644
--- a/filament/backend/include/backend/Program.h
+++ b/filament/backend/include/backend/Program.h
@@ -104,8 +104,9 @@ class Program {
Sampler const* samplers, size_t count) noexcept;
struct SpecializationConstant {
+ using Type = std::variant;
uint32_t id; // id set in glsl
- std::variant value; // value and type
+ Type value; // value and type
};
Program& specializationConstants(
diff --git a/filament/backend/include/backend/platforms/OpenGLPlatform.h b/filament/backend/include/backend/platforms/OpenGLPlatform.h
index c41dce43602..3f4488c5f53 100644
--- a/filament/backend/include/backend/platforms/OpenGLPlatform.h
+++ b/filament/backend/include/backend/platforms/OpenGLPlatform.h
@@ -288,6 +288,12 @@ class OpenGLPlatform : public Platform {
* @see terminate()
*/
virtual void createContext(bool shared);
+
+ /**
+ * Detach and destroy the current context if any and releases all resources associated to
+ * this thread.
+ */
+ virtual void releaseContext() noexcept;
};
} // namespace filament
diff --git a/filament/backend/include/backend/platforms/PlatformEGL.h b/filament/backend/include/backend/platforms/PlatformEGL.h
index 8902f14f767..79400540063 100644
--- a/filament/backend/include/backend/platforms/PlatformEGL.h
+++ b/filament/backend/include/backend/platforms/PlatformEGL.h
@@ -40,6 +40,7 @@ class PlatformEGL : public OpenGLPlatform {
PlatformEGL() noexcept;
bool isExtraContextSupported() const noexcept override;
void createContext(bool shared) override;
+ void releaseContext() noexcept override;
protected:
@@ -139,6 +140,7 @@ class PlatformEGL : public OpenGLPlatform {
bool KHR_create_context = false;
bool KHR_gl_colorspace = false;
bool KHR_no_config_context = false;
+ bool KHR_surfaceless_context = false;
} egl;
} ext;
diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc
index 2cb16a60fed..37ddd4c6ba0 100644
--- a/filament/backend/include/private/backend/DriverAPI.inc
+++ b/filament/backend/include/private/backend/DriverAPI.inc
@@ -142,7 +142,8 @@ DECL_DRIVER_API_N(setFrameScheduledCallback,
DECL_DRIVER_API_N(setFrameCompletedCallback,
backend::SwapChainHandle, sch,
- backend::FrameCompletedCallback, callback,
+ backend::CallbackHandler*, handler,
+ backend::CallbackHandler::Callback, callback,
void*, user)
DECL_DRIVER_API_N(setPresentationTime,
@@ -273,6 +274,7 @@ DECL_DRIVER_API_N(destroyRenderTarget, backend::RenderTargetHandle, rth)
DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch)
DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh)
DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh)
+DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh)
/*
* Synchronous APIs
@@ -286,7 +288,6 @@ DECL_DRIVER_API_SYNCHRONOUS_N(void, setAcquiredImage, backend::StreamHandle, str
DECL_DRIVER_API_SYNCHRONOUS_N(void, setStreamDimensions, backend::StreamHandle, stream, uint32_t, width, uint32_t, height)
DECL_DRIVER_API_SYNCHRONOUS_N(int64_t, getStreamTimestamp, backend::StreamHandle, stream)
DECL_DRIVER_API_SYNCHRONOUS_N(void, updateStreams, backend::DriverApi*, driver)
-DECL_DRIVER_API_SYNCHRONOUS_N(void, destroyFence, backend::FenceHandle, fh)
DECL_DRIVER_API_SYNCHRONOUS_N(backend::FenceStatus, getFenceStatus, backend::FenceHandle, fh)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatSupported, backend::TextureFormat, format)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isTextureSwizzleSupported)
@@ -297,6 +298,8 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameBufferFetchMultiSampleSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameTimeSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isAutoDepthResolveSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isSRGBSwapChainSupported)
+DECL_DRIVER_API_SYNCHRONOUS_0(bool, isStereoSupported)
+DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams)
diff --git a/filament/backend/src/CallbackHandler.cpp b/filament/backend/src/CallbackHandler.cpp
deleted file mode 100644
index a1c067b6d26..00000000000
--- a/filament/backend/src/CallbackHandler.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2021 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
-
-namespace filament::backend {
-
-CallbackHandler::~CallbackHandler() = default;
-
-} // namespace filament::backend
diff --git a/filament/backend/src/CompilerThreadPool.cpp b/filament/backend/src/CompilerThreadPool.cpp
index 008b353aaa1..591ae063c31 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 {
@@ -32,16 +34,14 @@ CompilerThreadPool::~CompilerThreadPool() noexcept {
assert_invariant(mQueues[1].empty());
}
-void CompilerThreadPool::init(uint32_t threadCount, JobSystem::Priority priority,
- ThreadSetup&& threadSetup) noexcept {
+void CompilerThreadPool::init(uint32_t threadCount,
+ ThreadSetup&& threadSetup, ThreadCleanup&& threadCleanup) noexcept {
auto setup = std::make_shared(std::move(threadSetup));
+ auto cleanup = std::make_shared(std::move(threadCleanup));
for (size_t i = 0; i < threadCount; i++) {
- mCompilerThreads.emplace_back([this, priority, setup]() {
- // give the thread a name
- JobSystem::setThreadName("CompilerThreadPool");
- // run at a slightly lower priority than other filament threads
- JobSystem::setThreadPriority(priority);
+ mCompilerThreads.emplace_back([this, setup, cleanup]() {
+ SYSTRACE_CONTEXT();
(*setup)();
@@ -53,7 +53,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& {
@@ -73,6 +77,8 @@ void CompilerThreadPool::init(uint32_t threadCount, JobSystem::Priority priority
job();
}
}
+
+ (*cleanup)();
});
}
diff --git a/filament/backend/src/CompilerThreadPool.h b/filament/backend/src/CompilerThreadPool.h
index 78ce4c70796..fbdff68f695 100644
--- a/filament/backend/src/CompilerThreadPool.h
+++ b/filament/backend/src/CompilerThreadPool.h
@@ -20,12 +20,13 @@
#include
#include
-#include
+#include
+#include
#include
-#include
#include
#include
+#include
#include
#include
@@ -45,8 +46,9 @@ class CompilerThreadPool {
~CompilerThreadPool() noexcept;
using Job = utils::Invocable;
using ThreadSetup = utils::Invocable;
- void init(uint32_t threadCount, utils::JobSystem::Priority priority,
- ThreadSetup&& threadSetup) noexcept;
+ using ThreadCleanup = utils::Invocable;
+ void init(uint32_t threadCount,
+ ThreadSetup&& threadSetup, ThreadCleanup&& threadCleanup) noexcept;
void terminate() noexcept;
void queue(CompilerPriorityQueue priorityQueue, program_token_t const& token, Job&& job);
Job dequeue(program_token_t const& token);
@@ -54,9 +56,9 @@ class CompilerThreadPool {
private:
using Queue = std::deque>;
std::vector mCompilerThreads;
- std::atomic_bool mExitRequested{false};
- std::mutex mQueueLock;
- std::condition_variable mQueueCondition;
+ bool mExitRequested{ false };
+ utils::Mutex mQueueLock;
+ utils::Condition mQueueCondition;
std::array mQueues;
// lock must be held for methods below
std::pair find(program_token_t const& token);
diff --git a/filament/backend/src/DriverBase.h b/filament/backend/src/DriverBase.h
index 3e7f2647d2f..abf68901164 100644
--- a/filament/backend/src/DriverBase.h
+++ b/filament/backend/src/DriverBase.h
@@ -165,13 +165,6 @@ class DriverBase : public Driver {
void purge() noexcept final;
- // --------------------------------------------------------------------------------------------
- // Privates
- // --------------------------------------------------------------------------------------------
-
-protected:
- class CallbackDataDetails;
-
// Helpers...
struct CallbackData {
CallbackData(CallbackData const &) = delete;
@@ -202,6 +195,13 @@ class DriverBase : public Driver {
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/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm
index 4476da77371..1d036d90a05 100644
--- a/filament/backend/src/metal/MetalDriver.mm
+++ b/filament/backend/src/metal/MetalDriver.mm
@@ -176,9 +176,9 @@
}
void MetalDriver::setFrameCompletedCallback(Handle sch,
- FrameCompletedCallback callback, void* user) {
+ CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
auto* swapChain = handle_cast(sch);
- swapChain->setFrameCompletedCallback(callback, user);
+ swapChain->setFrameCompletedCallback(handler, callback, user);
}
void MetalDriver::execute(std::function const& fn) noexcept {
@@ -696,6 +696,14 @@
return false;
}
+bool MetalDriver::isStereoSupported() {
+ return true;
+}
+
+bool MetalDriver::isParallelShaderCompileSupported() {
+ return false;
+}
+
bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
switch (workaround) {
case Workaround::SPLIT_EASU:
diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h
index 9ffa1a0bda8..b129d478d73 100644
--- a/filament/backend/src/metal/MetalHandles.h
+++ b/filament/backend/src/metal/MetalHandles.h
@@ -70,7 +70,8 @@ class MetalSwapChain : public HwSwapChain {
void releaseDrawable();
void setFrameScheduledCallback(FrameScheduledCallback callback, void* user);
- void setFrameCompletedCallback(FrameCompletedCallback callback, void* user);
+ void setFrameCompletedCallback(CallbackHandler* handler,
+ CallbackHandler::Callback callback, void* user);
// For CAMetalLayer-backed SwapChains, presents the drawable or schedules a
// FrameScheduledCallback.
@@ -112,8 +113,11 @@ class MetalSwapChain : public HwSwapChain {
FrameScheduledCallback frameScheduledCallback = nullptr;
void* frameScheduledUserData = nullptr;
- FrameCompletedCallback frameCompletedCallback = nullptr;
- void* frameCompletedUserData = nullptr;
+ struct {
+ CallbackHandler* handler = nullptr;
+ CallbackHandler::Callback callback = {};
+ void* user = nullptr;
+ } frameCompleted;
};
class MetalBufferObject : public HwBufferObject {
diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm
index 99e8b36227d..0b4d0b3c4dd 100644
--- a/filament/backend/src/metal/MetalHandles.mm
+++ b/filament/backend/src/metal/MetalHandles.mm
@@ -194,13 +194,15 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) {
frameScheduledUserData = user;
}
-void MetalSwapChain::setFrameCompletedCallback(FrameCompletedCallback callback, void* user) {
- frameCompletedCallback = callback;
- frameCompletedUserData = user;
+void MetalSwapChain::setFrameCompletedCallback(CallbackHandler* handler,
+ CallbackHandler::Callback callback, void* user) {
+ frameCompleted.handler = handler;
+ frameCompleted.callback = callback;
+ frameCompleted.user = user;
}
void MetalSwapChain::present() {
- if (frameCompletedCallback) {
+ if (frameCompleted.callback) {
scheduleFrameCompletedCallback();
}
if (drawable) {
@@ -244,30 +246,17 @@ void presentDrawable(bool presentFrame, void* user) {
}
void MetalSwapChain::scheduleFrameCompletedCallback() {
- if (!frameCompletedCallback) {
+ if (!frameCompleted.callback) {
return;
}
- FrameCompletedCallback callback = frameCompletedCallback;
- void* userData = frameCompletedUserData;
+ CallbackHandler* handler = frameCompleted.handler;
+ void* user = frameCompleted.user;
+ CallbackHandler::Callback callback = frameCompleted.callback;
+
+ MetalDriver* driver = context.driver;
[getPendingCommandBuffer(&context) addCompletedHandler:^(id cb) {
- struct CallbackData {
- void* userData;
- FrameCompletedCallback callback;
- };
- CallbackData* data = new CallbackData();
- data->userData = userData;
- data->callback = callback;
-
- // Instantiate a BufferDescriptor with a callback for the sole purpose of passing it to
- // scheduleDestroy. This forces the BufferDescriptor callback (and thus the
- // FrameCompletedCallback) to be called on the user thread.
- BufferDescriptor b(nullptr, 0u, [](void* buffer, size_t size, void* user) {
- CallbackData* data = (CallbackData*) user;
- data->callback(data->userData);
- free(data);
- }, data);
- context.driver->scheduleDestroy(std::move(b));
+ driver->scheduleCallback(handler, user, callback);
}];
}
diff --git a/filament/backend/src/noop/NoopDriver.cpp b/filament/backend/src/noop/NoopDriver.cpp
index 19b1cb5380f..3d1a9cdc327 100644
--- a/filament/backend/src/noop/NoopDriver.cpp
+++ b/filament/backend/src/noop/NoopDriver.cpp
@@ -58,7 +58,7 @@ void NoopDriver::setFrameScheduledCallback(Handle sch,
}
void NoopDriver::setFrameCompletedCallback(Handle sch,
- FrameCompletedCallback callback, void* user) {
+ CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
}
@@ -174,6 +174,14 @@ bool NoopDriver::isSRGBSwapChainSupported() {
return false;
}
+bool NoopDriver::isStereoSupported() {
+ return false;
+}
+
+bool NoopDriver::isParallelShaderCompileSupported() {
+ return false;
+}
+
bool NoopDriver::isWorkaroundNeeded(Workaround) {
return false;
}
diff --git a/filament/backend/src/opengl/CallbackManager.cpp b/filament/backend/src/opengl/CallbackManager.cpp
new file mode 100644
index 00000000000..8d85a9f4886
--- /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 00000000000..5349f201265
--- /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/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp
index e7a88f6b96d..072096718b3 100644
--- a/filament/backend/src/opengl/OpenGLContext.cpp
+++ b/filament/backend/src/opengl/OpenGLContext.cpp
@@ -49,6 +49,7 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept {
}
OpenGLContext::OpenGLContext() noexcept {
+
state.vao.p = &mDefaultVAO;
// These queries work with all GL/GLES versions!
@@ -61,265 +62,74 @@ OpenGLContext::OpenGLContext() noexcept {
"[" << state.version << "], [" << state.shader << "]" << io::endl;
/*
- * Figure out GL / GLES version and available features
+ * Figure out GL / GLES version, extensions and capabilities we need to
+ * determine the feature level
*/
queryOpenGLVersion(&state.major, &state.minor);
- glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &gets.max_renderbuffer_size);
- glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &gets.max_texture_image_units);
- glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gets.max_combined_texture_image_units);
+ OpenGLContext::initExtensions(&ext, state.major, state.minor);
- if (state.major > 2) { // this check works for both GL and GLES, but is intended for GLES
-#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
- glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &gets.max_uniform_block_size);
- glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &gets.max_uniform_buffer_bindings);
- glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &gets.uniform_buffer_offset_alignment);
- glGetIntegerv(GL_MAX_SAMPLES, &gets.max_samples);
- glGetIntegerv(GL_MAX_DRAW_BUFFERS, &gets.max_draw_buffers);
- glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
- &gets.max_transform_feedback_separate_attribs);
-#endif
- } else {
- gets.max_uniform_block_size = 0;
- gets.max_uniform_buffer_bindings = 0;
- gets.uniform_buffer_offset_alignment = 0;
- gets.max_samples = 1;
- gets.max_draw_buffers = 1;
- gets.max_transform_feedback_separate_attribs = 0;
- }
+ OpenGLContext::initProcs(&procs, ext, state.major, state.minor);
- constexpr auto const caps3 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_3];
- constexpr GLint MAX_VERTEX_SAMPLER_COUNT = caps3.MAX_VERTEX_SAMPLER_COUNT;
- constexpr GLint MAX_FRAGMENT_SAMPLER_COUNT = caps3.MAX_FRAGMENT_SAMPLER_COUNT;
+ OpenGLContext::initBugs(&bugs, ext, state.major, state.minor,
+ state.vendor, state.renderer, state.version, state.shader);
- // default procs that can be overridden based on runtime version
-#ifdef BACKEND_OPENGL_LEVEL_GLES30
- procs.genVertexArrays = glGenVertexArrays;
- procs.bindVertexArray = glBindVertexArray;
- procs.deleteVertexArrays = glDeleteVertexArrays;
+ glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &gets.max_renderbuffer_size);
+ glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &gets.max_texture_image_units);
+ glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gets.max_combined_texture_image_units);
- // these are core in GL and GLES 3.x
- procs.genQueries = glGenQueries;
- procs.deleteQueries = glDeleteQueries;
- procs.beginQuery = glBeginQuery;
- procs.endQuery = glEndQuery;
- procs.getQueryObjectuiv = glGetQueryObjectuiv;
-# ifdef BACKEND_OPENGL_VERSION_GL
- procs.getQueryObjectui64v = glGetQueryObjectui64v; // only core in GL
-# elif defined(GL_EXT_disjoint_timer_query)
- procs.getQueryObjectui64v = glGetQueryObjectui64vEXT;
-# endif // BACKEND_OPENGL_VERSION_GL
-
- // core in ES 3.0 and GL 4.3
- procs.invalidateFramebuffer = glInvalidateFramebuffer;
-#endif // BACKEND_OPENGL_LEVEL_GLES30
-
- // no-op if not supported
- procs.maxShaderCompilerThreadsKHR = +[](GLuint) {};
+ mFeatureLevel = OpenGLContext::resolveFeatureLevel(state.major, state.minor, ext, gets, bugs);
#ifdef BACKEND_OPENGL_VERSION_GLES
- initExtensionsGLES();
- if (state.major == 3) {
- // Runtime OpenGL version is ES 3.x
- assert_invariant(gets.max_texture_image_units >= 16);
- assert_invariant(gets.max_combined_texture_image_units >= 32);
- if (state.minor >= 1) {
- features.multisample_texture = true;
- // figure out our feature level
- if (ext.EXT_texture_cube_map_array) {
- mFeatureLevel = FeatureLevel::FEATURE_LEVEL_2;
- if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
- gets.max_combined_texture_image_units >=
- (MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
- mFeatureLevel = FeatureLevel::FEATURE_LEVEL_3;
- }
- }
- }
- }
-#ifndef IOS // IOS is guaranteed to have ES3.x
- else if (UTILS_UNLIKELY(state.major == 2)) {
- // Runtime OpenGL version is ES 2.x
-
-#if defined(BACKEND_OPENGL_LEVEL_GLES30)
- // mandatory extensions (all supported by Mali-400 and Adreno 304)
- assert_invariant(ext.OES_depth_texture);
- assert_invariant(ext.OES_depth24);
- assert_invariant(ext.OES_packed_depth_stencil);
- assert_invariant(ext.OES_rgb8_rgba8);
- assert_invariant(ext.OES_standard_derivatives);
- assert_invariant(ext.OES_texture_npot);
+ mShaderModel = ShaderModel::MOBILE;
+#else
+ mShaderModel = ShaderModel::DESKTOP;
#endif
- if (UTILS_LIKELY(ext.OES_vertex_array_object)) {
- procs.genVertexArrays = glGenVertexArraysOES;
- procs.bindVertexArray = glBindVertexArrayOES;
- procs.deleteVertexArrays = glDeleteVertexArraysOES;
- } else {
- // if we don't have OES_vertex_array_object, just don't do anything with real VAOs,
- // we'll just rebind everything each time. Most Mali-400 support this extension, but
- // a few don't.
- procs.genVertexArrays = +[](GLsizei, GLuint*) {};
- procs.bindVertexArray = +[](GLuint) {};
- procs.deleteVertexArrays = +[](GLsizei, GLuint const*) {};
- // we activate this workaround path, which does the reset of array buffer
- bugs.vao_doesnt_store_element_array_buffer_binding = true;
- }
-
- // EXT_disjoint_timer_query is optional -- pointers will be null if not available
- procs.genQueries = glGenQueriesEXT;
- procs.deleteQueries = glDeleteQueriesEXT;
- procs.beginQuery = glBeginQueryEXT;
- procs.endQuery = glEndQueryEXT;
- procs.getQueryObjectuiv = glGetQueryObjectuivEXT;
- procs.getQueryObjectui64v = glGetQueryObjectui64vEXT;
-
- procs.invalidateFramebuffer = glDiscardFramebufferEXT;
-
- procs.maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsKHR;
-
- mFeatureLevel = FeatureLevel::FEATURE_LEVEL_0;
+#ifdef BACKEND_OPENGL_VERSION_GLES
+ if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_2) {
+ features.multisample_texture = true;
}
-#endif // IOS
#else
- initExtensionsGL();
- if (state.major == 4) {
- assert_invariant(state.minor >= 1);
- mShaderModel = ShaderModel::DESKTOP;
- if (state.minor >= 3) {
- // cubemap arrays are available as of OpenGL 4.0
- mFeatureLevel = FeatureLevel::FEATURE_LEVEL_2;
- // figure out our feature level
- if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
- gets.max_combined_texture_image_units >=
- (MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
- mFeatureLevel = FeatureLevel::FEATURE_LEVEL_3;
- }
- }
+ if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) {
features.multisample_texture = true;
}
- // feedback loops are allowed on GL desktop as long as writes are disabled
- bugs.allow_read_only_ancillary_feedback_loop = true;
- assert_invariant(gets.max_texture_image_units >= 16);
- assert_invariant(gets.max_combined_texture_image_units >= 32);
-
- procs.maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsARB;
#endif
+ if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) {
+#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
+ glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE,
+ &gets.max_uniform_block_size);
+ glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS,
+ &gets.max_uniform_buffer_bindings);
+ glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT,
+ &gets.uniform_buffer_offset_alignment);
+ glGetIntegerv(GL_MAX_SAMPLES,
+ &gets.max_samples);
+ glGetIntegerv(GL_MAX_DRAW_BUFFERS,
+ &gets.max_draw_buffers);
+ glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
+ &gets.max_transform_feedback_separate_attribs);
#ifdef GL_EXT_texture_filter_anisotropic
- if (ext.EXT_texture_filter_anisotropic) {
- glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gets.max_anisotropy);
+ if (ext.EXT_texture_filter_anisotropic) {
+ glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gets.max_anisotropy);
+ }
+#endif
+#endif
+ }
+#ifdef BACKEND_OPENGL_VERSION_GLES
+ else {
+ gets.max_uniform_block_size = 0;
+ gets.max_uniform_buffer_bindings = 0;
+ gets.uniform_buffer_offset_alignment = 0;
+ gets.max_samples = 1;
+ gets.max_draw_buffers = 1;
+ gets.max_transform_feedback_separate_attribs = 0;
+ gets.max_anisotropy = 1;
}
#endif
- /*
- * Figure out which driver bugs we need to workaround
- */
-
- const bool isAngle = strstr(state.renderer, "ANGLE");
- if (!isAngle) {
- if (strstr(state.renderer, "Adreno")) {
- // Qualcomm GPU
- bugs.invalidate_end_only_if_invalidate_start = true;
-
- // On Adreno (As of 3/20) timer query seem to return the CPU time, not the GPU time.
- bugs.dont_use_timer_query = true;
-
- // Blits to texture arrays are failing
- // This bug continues to reproduce, though at times we've seen it appear to "go away".
- // The standalone sample app that was written to show this problem still reproduces.
- // The working hypothesis is that some other state affects this behavior.
- bugs.disable_blit_into_texture_array = true;
-
- // early exit condition is flattened in EASU code
- bugs.split_easu = true;
-
- // initialize the non-used uniform array for Adreno drivers.
- bugs.enable_initialize_non_used_uniform_array = true;
-
- int maj, min, driverMajor, driverMinor;
- int const c = sscanf(state.version, "OpenGL ES %d.%d V@%d.%d", // NOLINT(cert-err34-c)
- &maj, &min, &driverMajor, &driverMinor);
- if (c == 4) {
- // Workarounds based on version here.
- // notes:
- // bugs.invalidate_end_only_if_invalidate_start
- // - appeared at least in
- // "OpenGL ES 3.2 V@0490.0 (GIT@85da404, I46ff5fc46f, 1606794520) (Date:11/30/20)"
- // - wasn't present in
- // "OpenGL ES 3.2 V@0490.0 (GIT@0905e9f, Ia11ce2d146, 1599072951) (Date:09/02/20)"
- // - has been confirmed fixed in V@570.1 by Qualcomm
- if (driverMajor < 490 || driverMajor > 570 ||
- (driverMajor == 570 && driverMinor >= 1)) {
- bugs.invalidate_end_only_if_invalidate_start = false;
- }
- }
-
- // qualcomm seems to have no problem with this (which is good for us)
- bugs.allow_read_only_ancillary_feedback_loop = true;
- } else if (strstr(state.renderer, "Mali")) {
- // ARM GPU
- bugs.vao_doesnt_store_element_array_buffer_binding = true;
- if (strstr(state.renderer, "Mali-T")) {
- bugs.disable_glFlush = true;
- bugs.disable_shared_context_draws = true;
- bugs.texture_external_needs_rebind = true;
- // We have not verified that timer queries work on Mali-T, so we disable to be safe.
- bugs.dont_use_timer_query = true;
- }
- if (strstr(state.renderer, "Mali-G")) {
- // We have run into several problems with timer queries on Mali-Gxx:
- // - timer queries seem to cause memory corruptions in some cases on some devices
- // (see b/233754398)
- // - appeared at least in: "OpenGL ES 3.2 v1.r26p0-01eac0"
- // - wasn't present in: "OpenGL ES 3.2 v1.r32p1-00pxl1"
- // - timer queries sometime crash with an NPE (see b/273759031)
- bugs.dont_use_timer_query = true;
- }
- // Mali seems to have no problem with this (which is good for us)
- bugs.allow_read_only_ancillary_feedback_loop = true;
- } else if (strstr(state.renderer, "Intel")) {
- // Intel GPU
- bugs.vao_doesnt_store_element_array_buffer_binding = true;
- } else if (strstr(state.renderer, "PowerVR")) {
- // PowerVR GPU
- // On PowerVR (Rogue GE8320) glFlush doesn't seem to do anything, in particular,
- // it doesn't kick the GPU earlier, so don't issue these calls as they seem to slow
- // things down.
- bugs.disable_glFlush = true;
- // On PowerVR (Rogue GE8320) using gl_InstanceID too early in the shader doesn't work.
- bugs.powervr_shader_workarounds = true;
- // On PowerVR (Rogue GE8320) destroying a fbo after glBlitFramebuffer is effectively
- // equivalent to glFinish.
- bugs.delay_fbo_destruction = true;
- // PowerVR seems to have no problem with this (which is good for us)
- bugs.allow_read_only_ancillary_feedback_loop = true;
- // PowerVR has a shader compiler thread pinned on the last core
- bugs.disable_thread_affinity = true;
- } else if (strstr(state.renderer, "Apple")) {
- // Apple GPU
- } else if (strstr(state.renderer, "Tegra") ||
- strstr(state.renderer, "GeForce") ||
- strstr(state.renderer, "NV")) {
- // NVIDIA GPU
- } else if (strstr(state.renderer, "Vivante")) {
- // Vivante GPU
- } else if (strstr(state.renderer, "AMD") ||
- strstr(state.renderer, "ATI")) {
- // AMD/ATI GPU
- } else if (strstr(state.renderer, "Mozilla")) {
- bugs.disable_invalidate_framebuffer = true;
- }
- } else {
- // When running under ANGLE, it's a different set of workaround that we need.
- if (strstr(state.renderer, "Adreno")) {
- // Qualcomm GPU
- // early exit condition is flattened in EASU code
- // (that should be regardless of ANGLE, but we should double-check)
- bugs.split_easu = true;
- }
- // TODO: see if we could use `bugs.allow_read_only_ancillary_feedback_loop = true`
- }
slog.v << "Feature level: " << +mFeatureLevel << '\n';
slog.v << "Active workarounds: " << '\n';
@@ -345,14 +155,14 @@ OpenGLContext::OpenGLContext() noexcept {
#endif
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
- assert_invariant(state.major <= 2 || gets.max_draw_buffers >= 4); // minspec
+ 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 (state.major > 2 && ext.EXT_texture_filter_anisotropic) {
+ if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1 && ext.EXT_texture_filter_anisotropic) {
// make sure we don't have any error flag
while (glGetError() != GL_NO_ERROR) { }
@@ -458,9 +268,287 @@ void OpenGLContext::setDefaultState() noexcept {
}
}
+
+void OpenGLContext::initProcs(Procs* procs,
+ Extensions const& ext, GLint major, GLint) noexcept {
+ (void)ext;
+ (void)major;
+
+ // default procs that can be overridden based on runtime version
+#ifdef BACKEND_OPENGL_LEVEL_GLES30
+ procs->genVertexArrays = glGenVertexArrays;
+ procs->bindVertexArray = glBindVertexArray;
+ procs->deleteVertexArrays = glDeleteVertexArrays;
+
+ // these are core in GL and GLES 3.x
+ procs->genQueries = glGenQueries;
+ procs->deleteQueries = glDeleteQueries;
+ procs->beginQuery = glBeginQuery;
+ procs->endQuery = glEndQuery;
+ procs->getQueryObjectuiv = glGetQueryObjectuiv;
+# ifdef BACKEND_OPENGL_VERSION_GL
+ procs->getQueryObjectui64v = glGetQueryObjectui64v; // only core in GL
+# elif defined(GL_EXT_disjoint_timer_query)
+ procs->getQueryObjectui64v = glGetQueryObjectui64vEXT;
+# endif // BACKEND_OPENGL_VERSION_GL
+
+ // core in ES 3.0 and GL 4.3
+ procs->invalidateFramebuffer = glInvalidateFramebuffer;
+#endif // BACKEND_OPENGL_LEVEL_GLES30
+
+ // no-op if not supported
+ procs->maxShaderCompilerThreadsKHR = +[](GLuint) {};
+
+#ifdef BACKEND_OPENGL_VERSION_GLES
+# ifndef IOS // IOS is guaranteed to have ES3.x
+ if (UTILS_UNLIKELY(major == 2)) {
+ // Runtime OpenGL version is ES 2.x
+ if (UTILS_LIKELY(ext.OES_vertex_array_object)) {
+ procs->genVertexArrays = glGenVertexArraysOES;
+ procs->bindVertexArray = glBindVertexArrayOES;
+ procs->deleteVertexArrays = glDeleteVertexArraysOES;
+ } else {
+ // if we don't have OES_vertex_array_object, just don't do anything with real VAOs,
+ // we'll just rebind everything each time. Most Mali-400 support this extension, but
+ // a few don't.
+ procs->genVertexArrays = +[](GLsizei, GLuint*) {};
+ procs->bindVertexArray = +[](GLuint) {};
+ procs->deleteVertexArrays = +[](GLsizei, GLuint const*) {};
+ }
+
+ // EXT_disjoint_timer_query is optional -- pointers will be null if not available
+ procs->genQueries = glGenQueriesEXT;
+ procs->deleteQueries = glDeleteQueriesEXT;
+ procs->beginQuery = glBeginQueryEXT;
+ procs->endQuery = glEndQueryEXT;
+ procs->getQueryObjectuiv = glGetQueryObjectuivEXT;
+ procs->getQueryObjectui64v = glGetQueryObjectui64vEXT;
+
+ procs->invalidateFramebuffer = glDiscardFramebufferEXT;
+
+ procs->maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsKHR;
+ }
+# endif // IOS
+#else
+ procs->maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsARB;
+#endif
+}
+
+void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
+ GLint major, GLint minor,
+ char const* vendor,
+ char const* renderer,
+ char const* version,
+ char const* shader) {
+
+ (void)major;
+ (void)minor;
+ (void)vendor;
+ (void)renderer;
+ (void)version;
+ (void)shader;
+
+ const bool isAngle = strstr(renderer, "ANGLE");
+ if (!isAngle) {
+ if (strstr(renderer, "Adreno")) {
+ // Qualcomm GPU
+ bugs->invalidate_end_only_if_invalidate_start = true;
+
+ // On Adreno (As of 3/20) timer query seem to return the CPU time, not the GPU time.
+ bugs->dont_use_timer_query = true;
+
+ // Blits to texture arrays are failing
+ // This bug continues to reproduce, though at times we've seen it appear to "go away".
+ // The standalone sample app that was written to show this problem still reproduces.
+ // The working hypothesis is that some other state affects this behavior.
+ bugs->disable_blit_into_texture_array = true;
+
+ // early exit condition is flattened in EASU code
+ bugs->split_easu = true;
+
+ // initialize the non-used uniform array for Adreno drivers.
+ bugs->enable_initialize_non_used_uniform_array = true;
+
+ int maj, min, driverMajor, driverMinor;
+ int const c = sscanf(version, "OpenGL ES %d.%d V@%d.%d", // NOLINT(cert-err34-c)
+ &maj, &min, &driverMajor, &driverMinor);
+ if (c == 4) {
+ // Workarounds based on version here.
+ // Notes:
+ // bugs.invalidate_end_only_if_invalidate_start
+ // - appeared at least in
+ // "OpenGL ES 3.2 V@0490.0 (GIT@85da404, I46ff5fc46f, 1606794520) (Date:11/30/20)"
+ // - wasn't present in
+ // "OpenGL ES 3.2 V@0490.0 (GIT@0905e9f, Ia11ce2d146, 1599072951) (Date:09/02/20)"
+ // - has been confirmed fixed in V@570.1 by Qualcomm
+ if (driverMajor < 490 || driverMajor > 570 ||
+ (driverMajor == 570 && driverMinor >= 1)) {
+ bugs->invalidate_end_only_if_invalidate_start = false;
+ }
+ }
+
+ // qualcomm seems to have no problem with this (which is good for us)
+ bugs->allow_read_only_ancillary_feedback_loop = true;
+
+ // Older Adreno devices that support ES3.0 only tend to be extremely buggy, so we
+ // fall back to ES2.0.
+ if (major == 3 && minor == 0) {
+ bugs->force_feature_level0 = true;
+ }
+ } else if (strstr(renderer, "Mali")) {
+ // ARM GPU
+ bugs->vao_doesnt_store_element_array_buffer_binding = true;
+ if (strstr(renderer, "Mali-T")) {
+ bugs->disable_glFlush = true;
+ bugs->disable_shared_context_draws = true;
+ bugs->texture_external_needs_rebind = true;
+ // We have not verified that timer queries work on Mali-T, so we disable to be safe.
+ bugs->dont_use_timer_query = true;
+ }
+ if (strstr(renderer, "Mali-G")) {
+ // We have run into several problems with timer queries on Mali-Gxx:
+ // - timer queries seem to cause memory corruptions in some cases on some devices
+ // (see b/233754398)
+ // - appeared at least in: "OpenGL ES 3.2 v1.r26p0-01eac0"
+ // - wasn't present in: "OpenGL ES 3.2 v1.r32p1-00pxl1"
+ // - timer queries sometime crash with an NPE (see b/273759031)
+ bugs->dont_use_timer_query = true;
+ }
+ // Mali seems to have no problem with this (which is good for us)
+ bugs->allow_read_only_ancillary_feedback_loop = true;
+ } else if (strstr(renderer, "Intel")) {
+ // Intel GPU
+ bugs->vao_doesnt_store_element_array_buffer_binding = true;
+ } else if (strstr(renderer, "PowerVR")) {
+ // PowerVR GPU
+ // On PowerVR (Rogue GE8320) glFlush doesn't seem to do anything, in particular,
+ // it doesn't kick the GPU earlier, so don't issue these calls as they seem to slow
+ // things down.
+ bugs->disable_glFlush = true;
+ // On PowerVR (Rogue GE8320) using gl_InstanceID too early in the shader doesn't work.
+ bugs->powervr_shader_workarounds = true;
+ // On PowerVR (Rogue GE8320) destroying a fbo after glBlitFramebuffer is effectively
+ // equivalent to glFinish.
+ bugs->delay_fbo_destruction = true;
+ // PowerVR seems to have no problem with this (which is good for us)
+ bugs->allow_read_only_ancillary_feedback_loop = true;
+ // PowerVR has a shader compiler thread pinned on the last core
+ bugs->disable_thread_affinity = true;
+ } else if (strstr(renderer, "Apple")) {
+ // Apple GPU
+ } else if (strstr(renderer, "Tegra") ||
+ strstr(renderer, "GeForce") ||
+ strstr(renderer, "NV")) {
+ // NVIDIA GPU
+ } else if (strstr(renderer, "Vivante")) {
+ // Vivante GPU
+ } else if (strstr(renderer, "AMD") ||
+ strstr(renderer, "ATI")) {
+ // AMD/ATI GPU
+ } else if (strstr(renderer, "Mozilla")) {
+ bugs->disable_invalidate_framebuffer = true;
+ }
+ } else {
+ // When running under ANGLE, it's a different set of workaround that we need.
+ if (strstr(renderer, "Adreno")) {
+ // Qualcomm GPU
+ // early exit condition is flattened in EASU code
+ // (that should be regardless of ANGLE, but we should double-check)
+ bugs->split_easu = true;
+ }
+ // TODO: see if we could use `bugs.allow_read_only_ancillary_feedback_loop = true`
+ }
+
+#ifdef BACKEND_OPENGL_VERSION_GLES
+# ifndef IOS // IOS is guaranteed to have ES3.x
+ if (UTILS_UNLIKELY(major == 2)) {
+ if (UTILS_UNLIKELY(!exts.OES_vertex_array_object)) {
+ // we activate this workaround path, which does the reset of array buffer
+ bugs->vao_doesnt_store_element_array_buffer_binding = true;
+ }
+ }
+# endif // IOS
+#else
+ // feedback loops are allowed on GL desktop as long as writes are disabled
+ bugs->allow_read_only_ancillary_feedback_loop = true;
+#endif
+}
+
+FeatureLevel OpenGLContext::resolveFeatureLevel(GLint major, GLint minor,
+ Extensions const& exts,
+ Gets const& gets,
+ Bugs const& bugs) noexcept {
+
+ constexpr auto const caps3 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_3];
+ constexpr GLint MAX_VERTEX_SAMPLER_COUNT = caps3.MAX_VERTEX_SAMPLER_COUNT;
+ constexpr GLint MAX_FRAGMENT_SAMPLER_COUNT = caps3.MAX_FRAGMENT_SAMPLER_COUNT;
+
+ (void)exts;
+ (void)gets;
+ (void)bugs;
+
+ FeatureLevel featureLevel = FeatureLevel::FEATURE_LEVEL_1;
+
+#ifdef BACKEND_OPENGL_VERSION_GLES
+ if (major == 3) {
+ // Runtime OpenGL version is ES 3.x
+ assert_invariant(gets.max_texture_image_units >= 16);
+ assert_invariant(gets.max_combined_texture_image_units >= 32);
+ if (minor >= 1) {
+ // figure out our feature level
+ if (exts.EXT_texture_cube_map_array) {
+ featureLevel = FeatureLevel::FEATURE_LEVEL_2;
+ if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
+ gets.max_combined_texture_image_units >=
+ (MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
+ featureLevel = FeatureLevel::FEATURE_LEVEL_3;
+ }
+ }
+ }
+ }
+# ifndef IOS // IOS is guaranteed to have ES3.x
+ else if (UTILS_UNLIKELY(major == 2)) {
+ // Runtime OpenGL version is ES 2.x
+# if defined(BACKEND_OPENGL_LEVEL_GLES30)
+ // mandatory extensions (all supported by Mali-400 and Adreno 304)
+ assert_invariant(exts.OES_depth_texture);
+ assert_invariant(exts.OES_depth24);
+ assert_invariant(exts.OES_packed_depth_stencil);
+ assert_invariant(exts.OES_rgb8_rgba8);
+ assert_invariant(exts.OES_standard_derivatives);
+ assert_invariant(exts.OES_texture_npot);
+# endif
+ featureLevel = FeatureLevel::FEATURE_LEVEL_0;
+ }
+# endif // IOS
+#else
+ assert_invariant(gets.max_texture_image_units >= 16);
+ assert_invariant(gets.max_combined_texture_image_units >= 32);
+ if (major == 4) {
+ assert_invariant(minor >= 1);
+ if (minor >= 3) {
+ // cubemap arrays are available as of OpenGL 4.0
+ featureLevel = FeatureLevel::FEATURE_LEVEL_2;
+ // figure out our feature level
+ if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
+ gets.max_combined_texture_image_units >=
+ (MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
+ featureLevel = FeatureLevel::FEATURE_LEVEL_3;
+ }
+ }
+ }
+#endif
+
+ if (bugs.force_feature_level0) {
+ featureLevel = FeatureLevel::FEATURE_LEVEL_0;
+ }
+
+ return featureLevel;
+}
+
#ifdef BACKEND_OPENGL_VERSION_GLES
-void OpenGLContext::initExtensionsGLES() noexcept {
+void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor) noexcept {
const char * const extensions = (const char*)glGetString(GL_EXTENSIONS);
GLUtils::unordered_string_set const exts = GLUtils::split(extensions);
if constexpr (DEBUG_PRINT_EXTENSIONS) {
@@ -472,51 +560,50 @@ void OpenGLContext::initExtensionsGLES() noexcept {
// figure out and initialize the extensions we need
using namespace std::literals;
- ext.APPLE_color_buffer_packed_float = exts.has("GL_APPLE_color_buffer_packed_float"sv);
- ext.EXT_clip_control = exts.has("GL_EXT_clip_control"sv);
- ext.EXT_clip_cull_distance = exts.has("GL_EXT_clip_cull_distance"sv);
- ext.EXT_color_buffer_float = exts.has("GL_EXT_color_buffer_float"sv);
- ext.EXT_color_buffer_half_float = exts.has("GL_EXT_color_buffer_half_float"sv);
- ext.EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
- ext.EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
- ext.EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
- ext.EXT_multisampled_render_to_texture = exts.has("GL_EXT_multisampled_render_to_texture"sv);
- ext.EXT_multisampled_render_to_texture2 = exts.has("GL_EXT_multisampled_render_to_texture2"sv);
- ext.EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
+ ext->APPLE_color_buffer_packed_float = exts.has("GL_APPLE_color_buffer_packed_float"sv);
+ ext->EXT_clip_control = exts.has("GL_EXT_clip_control"sv);
+ ext->EXT_clip_cull_distance = exts.has("GL_EXT_clip_cull_distance"sv);
+ ext->EXT_color_buffer_float = exts.has("GL_EXT_color_buffer_float"sv);
+ ext->EXT_color_buffer_half_float = exts.has("GL_EXT_color_buffer_half_float"sv);
+ ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
+ ext->EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
+ ext->EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
+ ext->EXT_multisampled_render_to_texture = exts.has("GL_EXT_multisampled_render_to_texture"sv);
+ ext->EXT_multisampled_render_to_texture2 = exts.has("GL_EXT_multisampled_render_to_texture2"sv);
+ ext->EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
#if !defined(__EMSCRIPTEN__)
- ext.EXT_texture_compression_etc2 = true;
+ ext->EXT_texture_compression_etc2 = true;
#endif
- ext.EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
- ext.EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
- ext.EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
- ext.EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
- ext.EXT_texture_cube_map_array = exts.has("GL_EXT_texture_cube_map_array"sv) || exts.has("GL_OES_texture_cube_map_array"sv);
- ext.GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
- ext.KHR_debug = exts.has("GL_KHR_debug"sv);
- ext.KHR_parallel_shader_compile = exts.has("GL_KHR_parallel_shader_compile"sv);
- ext.KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
- ext.KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
- ext.OES_depth_texture = exts.has("GL_OES_depth_texture"sv);
- ext.OES_depth24 = exts.has("GL_OES_depth24"sv);
- ext.OES_packed_depth_stencil = exts.has("GL_OES_packed_depth_stencil"sv);
- ext.OES_EGL_image_external_essl3 = exts.has("GL_OES_EGL_image_external_essl3"sv);
- ext.OES_rgb8_rgba8 = exts.has("GL_OES_rgb8_rgba8"sv);
- ext.OES_standard_derivatives = exts.has("GL_OES_standard_derivatives"sv);
- ext.OES_texture_npot = exts.has("GL_OES_texture_npot"sv);
- ext.OES_vertex_array_object = exts.has("GL_OES_vertex_array_object"sv);
- ext.WEBGL_compressed_texture_etc = exts.has("WEBGL_compressed_texture_etc"sv);
- ext.WEBGL_compressed_texture_s3tc = exts.has("WEBGL_compressed_texture_s3tc"sv);
- ext.WEBGL_compressed_texture_s3tc_srgb = exts.has("WEBGL_compressed_texture_s3tc_srgb"sv);
+ ext->EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
+ ext->EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
+ ext->EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
+ ext->EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
+ ext->EXT_texture_cube_map_array = exts.has("GL_EXT_texture_cube_map_array"sv) || exts.has("GL_OES_texture_cube_map_array"sv);
+ ext->GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
+ ext->KHR_debug = exts.has("GL_KHR_debug"sv);
+ ext->KHR_parallel_shader_compile = exts.has("GL_KHR_parallel_shader_compile"sv);
+ ext->KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
+ ext->KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
+ ext->OES_depth_texture = exts.has("GL_OES_depth_texture"sv);
+ ext->OES_depth24 = exts.has("GL_OES_depth24"sv);
+ ext->OES_packed_depth_stencil = exts.has("GL_OES_packed_depth_stencil"sv);
+ ext->OES_EGL_image_external_essl3 = exts.has("GL_OES_EGL_image_external_essl3"sv);
+ ext->OES_rgb8_rgba8 = exts.has("GL_OES_rgb8_rgba8"sv);
+ ext->OES_standard_derivatives = exts.has("GL_OES_standard_derivatives"sv);
+ ext->OES_texture_npot = exts.has("GL_OES_texture_npot"sv);
+ ext->OES_vertex_array_object = exts.has("GL_OES_vertex_array_object"sv);
+ ext->WEBGL_compressed_texture_etc = exts.has("WEBGL_compressed_texture_etc"sv);
+ ext->WEBGL_compressed_texture_s3tc = exts.has("WEBGL_compressed_texture_s3tc"sv);
+ ext->WEBGL_compressed_texture_s3tc_srgb = exts.has("WEBGL_compressed_texture_s3tc_srgb"sv);
// ES 3.2 implies EXT_color_buffer_float
- if (state.major > 3 || (state.major == 3 && state.minor >= 2)) {
- ext.EXT_color_buffer_float = true;
+ if (major > 3 || (major == 3 && minor >= 2)) {
+ ext->EXT_color_buffer_float = true;
}
-
// ES 3.x implies EXT_discard_framebuffer and OES_vertex_array_object
- if (state.major >= 3) {
- ext.EXT_discard_framebuffer = true;
- ext.OES_vertex_array_object = true;
+ if (major >= 3) {
+ ext->EXT_discard_framebuffer = true;
+ ext->OES_vertex_array_object = true;
}
}
@@ -524,7 +611,7 @@ void OpenGLContext::initExtensionsGLES() noexcept {
#ifdef BACKEND_OPENGL_VERSION_GL
-void OpenGLContext::initExtensionsGL() noexcept {
+void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor) noexcept {
GLUtils::unordered_string_set exts;
GLint n = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &n);
@@ -539,55 +626,52 @@ void OpenGLContext::initExtensionsGL() noexcept {
}
using namespace std::literals;
- ext.APPLE_color_buffer_packed_float = true; // Assumes core profile.
- ext.ARB_shading_language_packing = exts.has("GL_ARB_shading_language_packing"sv);
- ext.EXT_color_buffer_float = true; // Assumes core profile.
- ext.EXT_color_buffer_half_float = true; // Assumes core profile.
- ext.EXT_clip_cull_distance = true;
- ext.EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
- ext.EXT_discard_framebuffer = false;
- ext.EXT_disjoint_timer_query = true;
- ext.EXT_multisampled_render_to_texture = false;
- ext.EXT_multisampled_render_to_texture2 = false;
- ext.EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
- ext.EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
- ext.EXT_texture_compression_etc2 = exts.has("GL_ARB_ES3_compatibility"sv);
- ext.EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
- ext.EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
- ext.EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
- ext.EXT_texture_cube_map_array = true;
- ext.EXT_texture_filter_anisotropic = exts.has("GL_EXT_texture_filter_anisotropic"sv);
- ext.EXT_texture_sRGB = exts.has("GL_EXT_texture_sRGB"sv);
- ext.GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
- ext.KHR_parallel_shader_compile = exts.has("GL_KHR_parallel_shader_compile"sv);
- ext.KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
- ext.KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
- ext.OES_depth_texture = true;
- ext.OES_depth24 = true;
- ext.OES_EGL_image_external_essl3 = false;
- ext.OES_rgb8_rgba8 = true;
- ext.OES_standard_derivatives = true;
- ext.OES_texture_npot = true;
- ext.OES_vertex_array_object = true;
- ext.WEBGL_compressed_texture_etc = false;
- ext.WEBGL_compressed_texture_s3tc = false;
- ext.WEBGL_compressed_texture_s3tc_srgb = false;
-
- auto const major = state.major;
- auto const minor = state.minor;
+ ext->APPLE_color_buffer_packed_float = true; // Assumes core profile.
+ ext->ARB_shading_language_packing = exts.has("GL_ARB_shading_language_packing"sv);
+ ext->EXT_color_buffer_float = true; // Assumes core profile.
+ ext->EXT_color_buffer_half_float = true; // Assumes core profile.
+ ext->EXT_clip_cull_distance = true;
+ ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
+ ext->EXT_discard_framebuffer = false;
+ ext->EXT_disjoint_timer_query = true;
+ ext->EXT_multisampled_render_to_texture = false;
+ ext->EXT_multisampled_render_to_texture2 = false;
+ ext->EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
+ ext->EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
+ ext->EXT_texture_compression_etc2 = exts.has("GL_ARB_ES3_compatibility"sv);
+ ext->EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
+ ext->EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
+ ext->EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
+ ext->EXT_texture_cube_map_array = true;
+ ext->EXT_texture_filter_anisotropic = exts.has("GL_EXT_texture_filter_anisotropic"sv);
+ ext->EXT_texture_sRGB = exts.has("GL_EXT_texture_sRGB"sv);
+ ext->GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
+ ext->KHR_parallel_shader_compile = exts.has("GL_KHR_parallel_shader_compile"sv);
+ ext->KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
+ ext->KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
+ ext->OES_depth_texture = true;
+ ext->OES_depth24 = true;
+ ext->OES_EGL_image_external_essl3 = false;
+ ext->OES_rgb8_rgba8 = true;
+ ext->OES_standard_derivatives = true;
+ ext->OES_texture_npot = true;
+ ext->OES_vertex_array_object = true;
+ ext->WEBGL_compressed_texture_etc = false;
+ ext->WEBGL_compressed_texture_s3tc = false;
+ ext->WEBGL_compressed_texture_s3tc_srgb = false;
// OpenGL 4.2 implies ARB_shading_language_packing
if (major > 4 || (major == 4 && minor >= 2)) {
- ext.ARB_shading_language_packing = true;
+ ext->ARB_shading_language_packing = true;
}
// OpenGL 4.3 implies EXT_discard_framebuffer
if (major > 4 || (major == 4 && minor >= 3)) {
- ext.EXT_discard_framebuffer = true;
- ext.KHR_debug = true;
+ ext->EXT_discard_framebuffer = true;
+ ext->KHR_debug = true;
}
// OpenGL 4.5 implies EXT_clip_control
if (major > 4 || (major == 4 && minor >= 5)) {
- ext.EXT_clip_control = true;
+ ext->EXT_clip_control = true;
}
}
@@ -683,7 +767,7 @@ void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum targe
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
- assert_invariant(state.major > 2 ||
+ 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) {
diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h
index 569d7749928..ff8b29cbd54 100644
--- a/filament/backend/src/opengl/OpenGLContext.h
+++ b/filament/backend/src/opengl/OpenGLContext.h
@@ -92,7 +92,7 @@ class OpenGLContext {
# ifndef BACKEND_OPENGL_LEVEL_GLES30
return true;
# else
- return state.major == 2;
+ return mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0;
# endif
#else
return false;
@@ -151,7 +151,7 @@ class OpenGLContext {
void deleteVertexArrays(GLsizei n, const GLuint* arrays) noexcept;
// glGet*() values
- struct {
+ struct Gets {
GLfloat max_anisotropy;
GLint max_draw_buffers;
GLint max_renderbuffer_size;
@@ -170,7 +170,7 @@ class OpenGLContext {
} features = {};
// supported extensions detected at runtime
- struct {
+ struct Extensions {
bool APPLE_color_buffer_packed_float;
bool ARB_shading_language_packing;
bool EXT_clip_control;
@@ -209,7 +209,7 @@ class OpenGLContext {
bool WEBGL_compressed_texture_s3tc_srgb;
} ext = {};
- struct {
+ struct Bugs {
// Some drivers have issues with UBOs in the fragment shader when
// glFlush() is called between draw calls.
bool disable_glFlush;
@@ -275,6 +275,10 @@ class OpenGLContext {
// performance more if we end-up pinned on the same one.
bool disable_thread_affinity;
+ // Force feature level 0. Typically used for low end ES3 devices with significant driver
+ // bugs or performance issues.
+ bool force_feature_level0;
+
} bugs = {};
// state getters -- as needed.
@@ -397,7 +401,7 @@ class OpenGLContext {
} window;
} state;
- struct {
+ struct Procs {
void (* bindVertexArray)(GLuint array);
void (* deleteVertexArrays)(GLsizei n, const GLuint* arrays);
void (* genVertexArrays)(GLsizei n, GLuint* arrays);
@@ -467,18 +471,46 @@ class OpenGLContext {
{ bugs.disable_thread_affinity,
"disable_thread_affinity",
""},
+ { bugs.force_feature_level0,
+ "force_feature_level0",
+ ""},
}};
RenderPrimitive mDefaultVAO;
// this is chosen to minimize code size
#if defined(BACKEND_OPENGL_VERSION_GLES)
- void initExtensionsGLES() noexcept;
+ static void initExtensionsGLES(Extensions* ext, GLint major, GLint minor) noexcept;
#endif
#if defined(BACKEND_OPENGL_VERSION_GL)
- void initExtensionsGL() noexcept;
+ static void initExtensionsGL(Extensions* ext, GLint major, GLint minor) noexcept;
#endif
+ static void initExtensions(Extensions* ext, GLint major, GLint minor) noexcept {
+#if defined(BACKEND_OPENGL_VERSION_GLES)
+ initExtensionsGLES(ext, major, minor);
+#endif
+#if defined(BACKEND_OPENGL_VERSION_GL)
+ initExtensionsGL(ext, major, minor);
+#endif
+ }
+
+ static void initBugs(Bugs* bugs, Extensions const& exts,
+ GLint major, GLint minor,
+ char const* vendor,
+ char const* renderer,
+ char const* version,
+ char const* shader
+ );
+
+ static void initProcs(Procs* procs,
+ Extensions const& exts, GLint major, GLint minor) noexcept;
+
+ static FeatureLevel resolveFeatureLevel(GLint major, GLint minor,
+ 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)) {
@@ -571,7 +603,7 @@ void OpenGLContext::activeTexture(GLuint unit) noexcept {
void OpenGLContext::bindSampler(GLuint unit, GLuint sampler) noexcept {
assert_invariant(unit < MAX_TEXTURE_UNIT_COUNT);
- assert_invariant(state.major > 2);
+ 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);
@@ -617,7 +649,7 @@ void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept {
void OpenGLContext::bindBufferRange(GLenum target, GLuint index, GLuint buffer,
GLintptr offset, GLsizeiptr size) noexcept {
- assert_invariant(state.major > 2);
+ assert_invariant(mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1);
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
# ifdef BACKEND_OPENGL_LEVEL_GLES31
diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp
index 47cc50edfc2..e2172ae8add 100644
--- a/filament/backend/src/opengl/OpenGLDriver.cpp
+++ b/filament/backend/src/opengl/OpenGLDriver.cpp
@@ -1872,6 +1872,18 @@ bool OpenGLDriver::isSRGBSwapChainSupported() {
return mPlatform.isSRGBSwapChainSupported();
}
+bool OpenGLDriver::isStereoSupported() {
+ // Stereo requires instancing and EXT_clip_cull_distance.
+ if (UTILS_UNLIKELY(mContext.isES2())) {
+ return false;
+ }
+ return mContext.ext.EXT_clip_cull_distance;
+}
+
+bool OpenGLDriver::isParallelShaderCompileSupported() {
+ return mShaderCompilerService.isParallelShaderCompileSupported();
+}
+
bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
switch (workaround) {
case Workaround::SPLIT_EASU:
@@ -2593,7 +2605,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);
}
}
@@ -3258,7 +3270,7 @@ void OpenGLDriver::setFrameScheduledCallback(Handle sch,
}
void OpenGLDriver::setFrameCompletedCallback(Handle sch,
- FrameCompletedCallback callback, void* user) {
+ CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
DEBUG_MARKER()
}
diff --git a/filament/backend/src/opengl/OpenGLPlatform.cpp b/filament/backend/src/opengl/OpenGLPlatform.cpp
index 4297479d97f..837faecdc4c 100644
--- a/filament/backend/src/opengl/OpenGLPlatform.cpp
+++ b/filament/backend/src/opengl/OpenGLPlatform.cpp
@@ -116,4 +116,7 @@ bool OpenGLPlatform::isExtraContextSupported() const noexcept {
void OpenGLPlatform::createContext(bool) {
}
+void OpenGLPlatform::releaseContext() noexcept {
+}
+
} // namespace filament::backend
diff --git a/filament/backend/src/opengl/ShaderCompilerService.cpp b/filament/backend/src/opengl/ShaderCompilerService.cpp
index 3dfdea5f478..d3d4cdd0658 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,11 +135,16 @@ 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) {
}
ShaderCompilerService::~ShaderCompilerService() noexcept = default;
+bool ShaderCompilerService::isParallelShaderCompileSupported() const noexcept {
+ return KHR_parallel_shader_compile || mShaderCompilerThreadCount;
+}
+
void ShaderCompilerService::init() noexcept {
// If we have KHR_parallel_shader_compile, we always use it, it should be more resource
// friendly.
@@ -170,32 +175,34 @@ void ShaderCompilerService::init() noexcept {
}
mShaderCompilerThreadCount = poolSize;
- mCompilerThreadPool.init(mShaderCompilerThreadCount, priority,
- [platform = &mDriver.mPlatform, sharedContext = mUseSharedContext]() {
+ mCompilerThreadPool.init(mShaderCompilerThreadCount,
+ [&platform = mDriver.mPlatform, priority]() {
+ // give the thread a name
+ JobSystem::setThreadName("CompilerThreadPool");
+ // run at a slightly lower priority than other filament threads
+ JobSystem::setThreadPriority(priority);
// create a gl context current to this thread
- platform->createContext(sharedContext);
+ platform.createContext(true);
+ },
+ [&platform = mDriver.mPlatform]() {
+ // release context and thread state
+ platform.releaseContext();
});
}
}
}
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 +210,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 {
-
- // compile the shaders
- std::array shaders{};
- std::array shaderSourceCode;
- compileShaders(gl,
- std::move(program.getShadersSource()),
- program.getSpecializationConstants(),
- shaders,
- shaderSourceCode);
-
- // link the program
- GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
-
- OpenGLProgramToken::ProgramBinary binary;
- binary.shaders = shaders;
-
- 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);
+ if (token->gl.program) {
+ return token;
+ }
- }
+ token->handle = mCallbackManager.get();
+
+ 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);
+
+ // link the program
+ GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
+
+ 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,31 +322,12 @@ 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);
+#ifndef FILAMENT_ENABLE_MATDBG
assert_invariant(program);
+#endif
return program;
}
@@ -395,74 +355,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 +401,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 668d4e31f24..0d8cb191929 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
@@ -65,16 +66,14 @@ class ShaderCompilerService {
~ShaderCompilerService() noexcept;
+ bool isParallelShaderCompileSupported() const noexcept;
+
void init() noexcept;
void terminate() noexcept;
// 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 +90,17 @@ class ShaderCompilerService {
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;
diff --git a/filament/backend/src/opengl/platforms/PlatformEGL.cpp b/filament/backend/src/opengl/platforms/PlatformEGL.cpp
index 08b20e0b42c..60652b54156 100644
--- a/filament/backend/src/opengl/platforms/PlatformEGL.cpp
+++ b/filament/backend/src/opengl/platforms/PlatformEGL.cpp
@@ -115,9 +115,14 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
auto extensions = GLUtils::split(eglQueryString(mEGLDisplay, EGL_EXTENSIONS));
ext.egl.ANDROID_recordable = extensions.has("EGL_ANDROID_recordable");
- ext.egl.KHR_create_context = extensions.has("EGL_KHR_create_context");
ext.egl.KHR_gl_colorspace = extensions.has("EGL_KHR_gl_colorspace");
+ ext.egl.KHR_create_context = extensions.has("EGL_KHR_create_context");
ext.egl.KHR_no_config_context = extensions.has("EGL_KHR_no_config_context");
+ ext.egl.KHR_surfaceless_context = extensions.has("KHR_surfaceless_context");
+ if (ext.egl.KHR_create_context) {
+ // KHR_create_context implies KHR_surfaceless_context for ES3.x contexts
+ ext.egl.KHR_surfaceless_context = true;
+ }
eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC) eglGetProcAddress("eglCreateSyncKHR");
eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC) eglGetProcAddress("eglDestroySyncKHR");
@@ -181,13 +186,6 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
eglConfig = mEGLConfig;
}
- // create the dummy surface, just for being able to make the context current.
- mEGLDummySurface = eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, pbufferAttribs);
- if (UTILS_UNLIKELY(mEGLDummySurface == EGL_NO_SURFACE)) {
- logEglError("eglCreatePbufferSurface");
- goto error;
- }
-
for (size_t tries = 0; tries < 3; tries++) {
mEGLContext = eglCreateContext(mEGLDisplay, eglConfig,
(EGLContext)sharedContext, contextAttribs.data());
@@ -220,6 +218,26 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
goto error;
}
+ if (ext.egl.KHR_surfaceless_context) {
+ // Adreno 306 driver advertises KHR_create_context but doesn't support passing
+ // EGL_NO_SURFACE to eglMakeCurrent with a 3.0 context.
+ if (UTILS_UNLIKELY(!eglMakeCurrent(mEGLDisplay,
+ EGL_NO_SURFACE, EGL_NO_SURFACE, mEGLContext))) {
+ if (eglGetError() == EGL_BAD_MATCH) {
+ ext.egl.KHR_surfaceless_context = false;
+ }
+ }
+ }
+
+ if (UTILS_UNLIKELY(!ext.egl.KHR_surfaceless_context)) {
+ // create the dummy surface, just for being able to make the context current.
+ mEGLDummySurface = eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, pbufferAttribs);
+ if (UTILS_UNLIKELY(mEGLDummySurface == EGL_NO_SURFACE)) {
+ logEglError("eglCreatePbufferSurface");
+ goto error;
+ }
+ }
+
if (UTILS_UNLIKELY(!makeCurrent(mEGLDummySurface, mEGLDummySurface))) {
// eglMakeCurrent failed
logEglError("eglMakeCurrent");
@@ -255,7 +273,7 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
}
bool PlatformEGL::isExtraContextSupported() const noexcept {
- return true;
+ return ext.egl.KHR_surfaceless_context;
}
void PlatformEGL::createContext(bool shared) {
@@ -276,6 +294,22 @@ void PlatformEGL::createContext(bool shared) {
mAdditionalContexts.push_back(context);
}
+void PlatformEGL::releaseContext() noexcept {
+ EGLContext context = eglGetCurrentContext();
+ eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ if (context != EGL_NO_CONTEXT) {
+ eglDestroyContext(mEGLDisplay, context);
+ }
+
+ mAdditionalContexts.erase(
+ std::remove_if(mAdditionalContexts.begin(), mAdditionalContexts.end(),
+ [context](EGLContext c) {
+ return c == context;
+ }), mAdditionalContexts.end());
+
+ eglReleaseThread();
+}
+
EGLBoolean PlatformEGL::makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept {
if (UTILS_UNLIKELY((drawSurface != mCurrentDrawSurface || readSurface != mCurrentReadSurface))) {
mCurrentDrawSurface = drawSurface;
@@ -286,8 +320,11 @@ EGLBoolean PlatformEGL::makeCurrent(EGLSurface drawSurface, EGLSurface readSurfa
}
void PlatformEGL::terminate() noexcept {
+ // it's always allowed to use EGL_NO_SURFACE, EGL_NO_CONTEXT
eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- eglDestroySurface(mEGLDisplay, mEGLDummySurface);
+ if (mEGLDummySurface) {
+ eglDestroySurface(mEGLDisplay, mEGLDummySurface);
+ }
eglDestroyContext(mEGLDisplay, mEGLContext);
for (auto context : mAdditionalContexts) {
eglDestroyContext(mEGLDisplay, context);
diff --git a/filament/backend/src/vulkan/VulkanContext.h b/filament/backend/src/vulkan/VulkanContext.h
index a22951deb61..2dba9d3bdeb 100644
--- a/filament/backend/src/vulkan/VulkanContext.h
+++ b/filament/backend/src/vulkan/VulkanContext.h
@@ -97,8 +97,7 @@ struct VulkanContext {
}
flags >>= 1;
}
- ASSERT_POSTCONDITION(false, "Unable to find a memory type that meets requirements.");
- return (uint32_t) ~0ul;
+ return (uint32_t) VK_MAX_MEMORY_TYPES;
}
inline VkFormat getDepthFormat() const {
diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp
index 374f6ffe876..c1c35014e6b 100644
--- a/filament/backend/src/vulkan/VulkanDriver.cpp
+++ b/filament/backend/src/vulkan/VulkanDriver.cpp
@@ -276,7 +276,7 @@ void VulkanDriver::setFrameScheduledCallback(Handle sch,
}
void VulkanDriver::setFrameCompletedCallback(Handle sch,
- FrameCompletedCallback callback, void* user) {
+ CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
}
void VulkanDriver::setPresentationTime(int64_t monotonic_clock_ns) {
@@ -755,6 +755,14 @@ bool VulkanDriver::isSRGBSwapChainSupported() {
return mPlatform->isSRGBSwapChainSupported();
}
+bool VulkanDriver::isStereoSupported() {
+ return true;
+}
+
+bool VulkanDriver::isParallelShaderCompileSupported() {
+ return false;
+}
+
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
switch (workaround) {
case Workaround::SPLIT_EASU: {
diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp
index e3d4579cd37..29a9f023ba5 100644
--- a/filament/backend/src/vulkan/VulkanHandles.cpp
+++ b/filament/backend/src/vulkan/VulkanHandles.cpp
@@ -87,21 +87,26 @@ VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept :
};
for (size_t i = 0; i < specializationConstants.size(); i++) {
- const uint32_t offset = uint32_t(i) * 4;
- std::visit([&](auto&& arg) {
- using T = std::decay_t;
- pEntries[i] = {
- .constantID = specializationConstants[i].id,
- .offset = offset,
- // Turns out vulkan expects the size of bool to be 4 (verified through
- // validation layer). So all expected types are of 4 bytes.
- .size = 4,
- };
- T* const addr = (T*)((char*)pData + offset);
- *addr = arg;
- }, specializationConstants[i].value);
+ uint32_t const offset = uint32_t(i) * 4;
+ pEntries[i] = {
+ .constantID = specializationConstants[i].id,
+ .offset = offset,
+ // Note that bools are 4-bytes in Vulkan
+ // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBool32.html
+ .size = 4,
+ };
+
+ using SpecConstant = Program::SpecializationConstant::Type;
+ char const* addr = (char*)pData + offset;
+ SpecConstant const& arg = specializationConstants[i].value;
+ if (std::holds_alternative(arg)) {
+ *((VkBool32*)addr) = std::get(arg) ? VK_TRUE : VK_FALSE;
+ } else if (std::holds_alternative(arg)) {
+ *((float*)addr) = std::get(arg);
+ } else {
+ *((int32_t*)addr) = std::get(arg);
+ }
}
-
bundle.specializationInfos = pInfo;
}
diff --git a/filament/backend/src/vulkan/VulkanReadPixels.cpp b/filament/backend/src/vulkan/VulkanReadPixels.cpp
index e299d206597..fd51344b830 100644
--- a/filament/backend/src/vulkan/VulkanReadPixels.cpp
+++ b/filament/backend/src/vulkan/VulkanReadPixels.cpp
@@ -176,12 +176,28 @@ void VulkanReadPixels::run(VulkanRenderTarget const* srcTarget, uint32_t const x
VkMemoryRequirements memReqs;
VkDeviceMemory stagingMemory;
vkGetImageMemoryRequirements(device, stagingImage, &memReqs);
+
+ uint32_t memoryTypeIndex = selectMemoryFunc(memReqs.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+ | VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
+
+ // If VK_MEMORY_PROPERTY_HOST_CACHED_BIT is not supported, we try only
+ // HOST_VISIBLE+HOST_COHERENT. HOST_CACHED helps a lot with readpixels performance.
+ if (memoryTypeIndex >= VK_MAX_MEMORY_TYPES) {
+ memoryTypeIndex = selectMemoryFunc(memReqs.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+ utils::slog.w
+ << "readPixels is slow because VK_MEMORY_PROPERTY_HOST_CACHED_BIT is not available"
+ << utils::io::endl;
+ }
+
+ ASSERT_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES,
+ "VulkanReadPixels: unable to find a memory type that meets requirements.");
+
VkMemoryAllocateInfo const allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
- .memoryTypeIndex = selectMemoryFunc(memReqs.memoryTypeBits,
- VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
- | VK_MEMORY_PROPERTY_HOST_CACHED_BIT),
+ .memoryTypeIndex = memoryTypeIndex,
};
vkAllocateMemory(device, &allocInfo, VKALLOC, &stagingMemory);
diff --git a/filament/backend/src/vulkan/VulkanTexture.cpp b/filament/backend/src/vulkan/VulkanTexture.cpp
index 6b86def4e8a..8df63626a3d 100644
--- a/filament/backend/src/vulkan/VulkanTexture.cpp
+++ b/filament/backend/src/vulkan/VulkanTexture.cpp
@@ -167,11 +167,17 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
// Allocate memory for the VkImage and bind it.
VkMemoryRequirements memReqs = {};
vkGetImageMemoryRequirements(mDevice, mTextureImage, &memReqs);
+
+ uint32_t memoryTypeIndex
+ = context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+ ASSERT_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES,
+ "VulkanTexture: unable to find a memory type that meets requirements.");
+
VkMemoryAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
- .memoryTypeIndex = context.selectMemoryType(memReqs.memoryTypeBits,
- VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
+ .memoryTypeIndex = memoryTypeIndex,
};
error = vkAllocateMemory(mDevice, &allocInfo, nullptr, &mTextureImageMemory);
ASSERT_POSTCONDITION(!error, "Unable to allocate image memory.");
diff --git a/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp b/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
index 41a4f891115..f83e8be93b9 100644
--- a/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
+++ b/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
@@ -57,11 +57,17 @@ std::tuple createImageAndMemory(VulkanContext const& co
VkDeviceMemory imageMemory;
VkMemoryRequirements memReqs;
vkGetImageMemoryRequirements(device, image, &memReqs);
+
+ uint32_t memoryTypeIndex
+ = context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+ ASSERT_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES,
+ "VulkanPlatformSwapChainImpl: unable to find a memory type that meets requirements.");
+
VkMemoryAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
- .memoryTypeIndex
- = context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
+ .memoryTypeIndex = memoryTypeIndex,
};
result = vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory);
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to allocate image memory.");
diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h
index e4d601b3cde..f4173f26144 100644
--- a/filament/include/filament/Engine.h
+++ b/filament/include/filament/Engine.h
@@ -513,6 +513,14 @@ class UTILS_PUBLIC Engine {
*/
size_t getMaxAutomaticInstances() const noexcept;
+ /**
+ * Queries the device and platform for instanced stereo rendering support.
+ *
+ * @return true if stereo rendering is supported, false otherwise
+ * @see View::setStereoscopicOptions
+ */
+ bool isStereoSupported() const noexcept;
+
/**
* @return EntityManager used by filament
*/
diff --git a/filament/include/filament/SwapChain.h b/filament/include/filament/SwapChain.h
index 9f7a328199e..29413275a42 100644
--- a/filament/include/filament/SwapChain.h
+++ b/filament/include/filament/SwapChain.h
@@ -18,10 +18,13 @@
#define TNT_FILAMENT_SWAPCHAIN_H
#include
+
+#include
#include
#include
#include
+#include
namespace filament {
@@ -148,7 +151,7 @@ class Engine;
class UTILS_PUBLIC SwapChain : public FilamentAPI {
public:
using FrameScheduledCallback = backend::FrameScheduledCallback;
- using FrameCompletedCallback = backend::FrameCompletedCallback;
+ using FrameCompletedCallback = utils::Invocable;
/**
* Requests a SwapChain with an alpha channel.
@@ -241,17 +244,23 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI {
* contents have completed rendering on the GPU.
*
* Use SwapChain::setFrameCompletedCallback to set a callback on an individual SwapChain. Each
- * time a frame completes GPU rendering, the callback will be called with optional user data.
+ * time a frame completes GPU rendering, the callback will be called.
*
- * The FrameCompletedCallback is guaranteed to be called on the main Filament thread.
+ * If handler is nullptr, the callback is guaranteed to be called on the main Filament thread.
*
- * @param callback A callback, or nullptr to unset.
- * @param user An optional pointer to user data passed to the callback function.
+ * Use \c setFrameCompletedCallback() (with default arguments) to unset the callback.
+ *
+ * @param handler Handler to dispatch the callback or nullptr for the default handler.
+ * @param callback Callback called when each frame completes.
*
* @remark Only Filament's Metal backend supports frame callbacks. Other backends ignore the
* callback (which will never be called) and proceed normally.
+ *
+ * @see CallbackHandler
*/
- void setFrameCompletedCallback(FrameCompletedCallback callback, void* user = nullptr);
+ void setFrameCompletedCallback(backend::CallbackHandler* handler = nullptr,
+ FrameCompletedCallback&& callback = {}) noexcept;
+
};
} // namespace filament
diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h
index b19ea3bfe07..7c0f1683e42 100644
--- a/filament/include/filament/View.h
+++ b/filament/include/filament/View.h
@@ -689,9 +689,12 @@ class UTILS_PUBLIC View : public FilamentAPI {
* - shadowing
* - punctual lights
*
+ * Stereo rendering depends on device and platform support. To check if stereo rendering is
+ * supported, use Engine::isStereoSupported().
+ *
* @param options The stereoscopic options to use on this view
*/
- void setStereoscopicOptions(StereoscopicOptions const& options) noexcept;
+ void setStereoscopicOptions(StereoscopicOptions const& options);
/**
* Returns the stereoscopic options associated with this View.
diff --git a/filament/src/Engine.cpp b/filament/src/Engine.cpp
index ecedc590efb..01b956484c4 100644
--- a/filament/src/Engine.cpp
+++ b/filament/src/Engine.cpp
@@ -327,6 +327,10 @@ size_t Engine::getMaxAutomaticInstances() const noexcept {
return downcast(this)->getMaxAutomaticInstances();
}
+bool Engine::isStereoSupported() const noexcept {
+ return downcast(this)->isStereoSupported();
+}
+
#if defined(__EMSCRIPTEN__)
void Engine::resetBackendState() noexcept {
downcast(this)->resetBackendState();
diff --git a/filament/src/Froxelizer.cpp b/filament/src/Froxelizer.cpp
index 995fc76af24..52e0d71a845 100644
--- a/filament/src/Froxelizer.cpp
+++ b/filament/src/Froxelizer.cpp
@@ -81,6 +81,21 @@ struct Froxelizer::FroxelThreadData :
public std::array {
};
+
+// Returns false if the two matrices are different. May return false if they're the
+// same, with some elements only differing by +0 or -0. Behaviour is undefined with NaNs.
+static bool fuzzyEqual(mat4f const& UTILS_RESTRICT l, mat4f const& UTILS_RESTRICT r) noexcept {
+ auto const li = reinterpret_cast( reinterpret_cast(&l) );
+ auto const ri = reinterpret_cast( reinterpret_cast(&r) );
+ uint32_t result = 0;
+ for (size_t i = 0; i < sizeof(mat4f) / sizeof(uint32_t); i++) {
+ // clang fully vectorizes this
+ result |= li[i] ^ ri[i];
+ }
+ return result == 0;
+}
+
+
Froxelizer::Froxelizer(FEngine& engine)
: mArena("froxel", PER_FROXELDATA_ARENA_SIZE),
mZLightNear(FROXEL_FIRST_SLICE_DEPTH),
@@ -144,9 +159,8 @@ void Froxelizer::setViewport(filament::Viewport const& viewport) noexcept {
}
void Froxelizer::setProjection(const mat4f& projection,
- float near,
- UTILS_UNUSED float far) noexcept {
- if (UTILS_UNLIKELY(mat4f::fuzzyEqual(mProjection, projection))) {
+ float near, UTILS_UNUSED float far) noexcept {
+ if (UTILS_UNLIKELY(!fuzzyEqual(mProjection, projection))) {
mProjection = projection;
mNear = near;
mDirtyFlags |= PROJECTION_CHANGED;
diff --git a/filament/src/SwapChain.cpp b/filament/src/SwapChain.cpp
index ae1498cc916..c30bce69416 100644
--- a/filament/src/SwapChain.cpp
+++ b/filament/src/SwapChain.cpp
@@ -28,8 +28,9 @@ void SwapChain::setFrameScheduledCallback(FrameScheduledCallback callback, void*
return downcast(this)->setFrameScheduledCallback(callback, user);
}
-void SwapChain::setFrameCompletedCallback(FrameCompletedCallback callback, void* user) {
- return downcast(this)->setFrameCompletedCallback(callback, user);
+void SwapChain::setFrameCompletedCallback(backend::CallbackHandler* handler,
+ utils::Invocable&& callback) noexcept {
+ return downcast(this)->setFrameCompletedCallback(handler, std::move(callback));
}
bool SwapChain::isSRGBSwapChainSupported(Engine& engine) noexcept {
diff --git a/filament/src/View.cpp b/filament/src/View.cpp
index bc5da818290..dd8e9380a75 100644
--- a/filament/src/View.cpp
+++ b/filament/src/View.cpp
@@ -283,7 +283,7 @@ bool View::isStencilBufferEnabled() const noexcept {
return downcast(this)->isStencilBufferEnabled();
}
-void View::setStereoscopicOptions(const StereoscopicOptions& options) noexcept {
+void View::setStereoscopicOptions(const StereoscopicOptions& options) {
return downcast(this)->setStereoscopicOptions(options);
}
diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h
index 134cbe5067c..d9a7caeb917 100644
--- a/filament/src/details/Engine.h
+++ b/filament/src/details/Engine.h
@@ -182,6 +182,8 @@ class FEngine : public Engine {
return CONFIG_MAX_INSTANCES;
}
+ bool isStereoSupported() const noexcept { return getDriver().isStereoSupported(); }
+
PostProcessManager const& getPostProcessManager() const noexcept {
return mPostProcessManager;
}
diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp
index 9b3f8341572..f9cf7405cdd 100644
--- a/filament/src/details/Material.cpp
+++ b/filament/src/details/Material.cpp
@@ -478,15 +478,22 @@ void FMaterial::compile(CompilerPriorityQueue priority,
backend::CallbackHandler* handler,
utils::Invocable&& callback) noexcept {
+ // Turn off the STE variant if stereo is not supported.
+ if (!mEngine.getDriverApi().isStereoSupported()) {
+ variantSpec &= ~UserVariantFilterMask(UserVariantFilterBit::STE);
+ }
+
UserVariantFilterMask const variantFilter =
~variantSpec & UserVariantFilterMask(UserVariantFilterBit::ALL);
- auto const& variants = isVariantLit() ?
- VariantUtils::getLitVariants() : VariantUtils::getUnlitVariants();
- for (auto const variant : variants) {
- if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) {
- if (hasVariant(variant)) {
- prepareProgram(variant, priority);
+ if (UTILS_LIKELY(mEngine.getDriverApi().isParallelShaderCompileSupported())) {
+ auto const& variants = isVariantLit() ?
+ VariantUtils::getLitVariants() : VariantUtils::getUnlitVariants();
+ for (auto const variant: variants) {
+ if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) {
+ if (hasVariant(variant)) {
+ prepareProgram(variant, priority);
+ }
}
}
}
diff --git a/filament/src/details/Material.h b/filament/src/details/Material.h
index a5372249063..e4b6f975c93 100644
--- a/filament/src/details/Material.h
+++ b/filament/src/details/Material.h
@@ -101,7 +101,7 @@ class FMaterial : public Material {
// Must be called after prepareProgram().
[[nodiscard]] backend::Handle getProgram(Variant variant) const noexcept {
#if FILAMENT_ENABLE_MATDBG
- assert_invariant(variant.key < VARIANT_COUNT);
+ assert_invariant((size_t)variant.key < VARIANT_COUNT);
std::unique_lock lock(mActiveProgramsLock);
mActivePrograms.set(variant.key);
lock.unlock();
diff --git a/filament/src/details/SwapChain.cpp b/filament/src/details/SwapChain.cpp
index ba13be2e2d3..d9cb80911d9 100644
--- a/filament/src/details/SwapChain.cpp
+++ b/filament/src/details/SwapChain.cpp
@@ -38,8 +38,24 @@ void FSwapChain::setFrameScheduledCallback(FrameScheduledCallback callback, void
mEngine.getDriverApi().setFrameScheduledCallback(mSwapChain, callback, user);
}
-void FSwapChain::setFrameCompletedCallback(FrameCompletedCallback callback, void* user) {
- mEngine.getDriverApi().setFrameCompletedCallback(mSwapChain, callback, user);
+void FSwapChain::setFrameCompletedCallback(backend::CallbackHandler* handler,
+ utils::Invocable&& callback) noexcept {
+ struct Callback {
+ utils::Invocable f;
+ SwapChain* s;
+ static void func(void* user) {
+ auto* const c = reinterpret_cast(user);
+ c->f(c->s);
+ delete c;
+ }
+ };
+ if (callback) {
+ auto* const user = new(std::nothrow) Callback{ std::move(callback), this };
+ mEngine.getDriverApi().setFrameCompletedCallback(
+ mSwapChain, handler, &Callback::func, static_cast(user));
+ } else {
+ mEngine.getDriverApi().setFrameCompletedCallback(mSwapChain, nullptr, nullptr, nullptr);
+ }
}
bool FSwapChain::isSRGBSwapChainSupported(FEngine& engine) noexcept {
diff --git a/filament/src/details/SwapChain.h b/filament/src/details/SwapChain.h
index c1a3f436d2c..032b5e3f914 100644
--- a/filament/src/details/SwapChain.h
+++ b/filament/src/details/SwapChain.h
@@ -23,6 +23,9 @@
#include
+#include
+
+#include
#include
namespace filament {
@@ -61,7 +64,8 @@ class FSwapChain : public SwapChain {
void setFrameScheduledCallback(FrameScheduledCallback callback, void* user);
- void setFrameCompletedCallback(FrameCompletedCallback callback, void* user);
+ void setFrameCompletedCallback(backend::CallbackHandler* handler,
+ utils::Invocable&& callback) noexcept;
static bool isSRGBSwapChainSupported(FEngine& engine) noexcept;
diff --git a/filament/src/details/View.cpp b/filament/src/details/View.cpp
index bb85c059bf8..5b932a42b7f 100644
--- a/filament/src/details/View.cpp
+++ b/filament/src/details/View.cpp
@@ -58,6 +58,7 @@ static constexpr float PID_CONTROLLER_Kd = 0.0f;
FView::FView(FEngine& engine)
: mFroxelizer(engine),
mFogEntity(engine.getEntityManager().create()),
+ mIsStereoSupported(engine.getDriverApi().isStereoSupported()),
mPerViewUniforms(engine),
mShadowMapManager(engine) {
DriverApi& driver = engine.getDriverApi();
@@ -1117,7 +1118,9 @@ View::PickingQuery& FView::pick(uint32_t x, uint32_t y, backend::CallbackHandler
return *pQuery;
}
-void FView::setStereoscopicOptions(const StereoscopicOptions& options) noexcept {
+void FView::setStereoscopicOptions(const StereoscopicOptions& options) {
+ ASSERT_PRECONDITION(!options.enabled || mIsStereoSupported,
+ "Stereo rendering is not supported.");
mStereoscopicOptions = options;
}
diff --git a/filament/src/details/View.h b/filament/src/details/View.h
index 60122aec4fd..f452ce22f18 100644
--- a/filament/src/details/View.h
+++ b/filament/src/details/View.h
@@ -193,7 +193,7 @@ class FView : public View {
bool isStencilBufferEnabled() const noexcept { return mStencilBufferEnabled; }
- void setStereoscopicOptions(StereoscopicOptions const& options) noexcept;
+ void setStereoscopicOptions(StereoscopicOptions const& options);
FCamera const* getDirectionalLightCamera() const noexcept {
return &mShadowMapManager.getShadowMap(0)->getDebugCamera();
@@ -524,6 +524,7 @@ class FView : public View {
const FColorGrading* mColorGrading = nullptr;
const FColorGrading* mDefaultColorGrading = nullptr;
utils::Entity mFogEntity{};
+ bool mIsStereoSupported : 1;
PIDController mPidController;
DynamicResolutionOptions mDynamicResolution;
diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec
index 33db11f276e..8c09b6521b7 100644
--- a/ios/CocoaPods/Filament.podspec
+++ b/ios/CocoaPods/Filament.podspec
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
- spec.version = "1.42.0"
+ spec.version = "1.42.1"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
- spec.source = { :http => "https://github.com/google/filament/releases/download/v1.42.0/filament-v1.42.0-ios.tgz" }
+ spec.source = { :http => "https://github.com/google/filament/releases/download/v1.42.1/filament-v1.42.1-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {
diff --git a/libs/filamat/src/shaders/CodeGenerator.cpp b/libs/filamat/src/shaders/CodeGenerator.cpp
index 90a20f321a3..b854ff3fe32 100644
--- a/libs/filamat/src/shaders/CodeGenerator.cpp
+++ b/libs/filamat/src/shaders/CodeGenerator.cpp
@@ -239,7 +239,10 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade
generateSpecializationConstant(out, "CONFIG_POWER_VR_SHADER_WORKAROUNDS",
+ReservedSpecializationConstants::CONFIG_POWER_VR_SHADER_WORKAROUNDS, false);
- out << "const int CONFIG_STEREOSCOPIC_EYES = " << (int)CONFIG_STEREOSCOPIC_EYES << ";\n";
+ // CONFIG_STEREOSCOPIC_EYES is used to size arrays and on Adreno GPUs + vulkan, this has to
+ // be explicitly, statically defined (as in #define). Otherwise (using const int for
+ // example), we'd run into a GPU crash.
+ out << "#define CONFIG_STEREOSCOPIC_EYES " << (int) CONFIG_STEREOSCOPIC_EYES << "\n";
if (material.featureLevel == 0) {
// On ES2 since we don't have post-processing, we need to emulate EGL_GL_COLORSPACE_KHR,
diff --git a/libs/math/include/math/mat2.h b/libs/math/include/math/mat2.h
index 551fe4451ed..dba9ca47230 100644
--- a/libs/math/include/math/mat2.h
+++ b/libs/math/include/math/mat2.h
@@ -235,23 +235,6 @@ class MATH_EMPTY_BASES TMat22 :
return r;
}
- // returns false if the two matrices are different. May return false if they're the
- // same, with some elements only differing by +0 or -0. Behaviour is undefined with NaNs.
- static constexpr bool fuzzyEqual(TMat22 l, TMat22 r) noexcept {
- uint64_t const* const li = reinterpret_cast(&l);
- uint64_t const* const ri = reinterpret_cast(&r);
- uint64_t result = 0;
- // For some reason clang is not able to vectoize this loop when the number of iteration
- // is known and constant (!?!?!). Still this is better than operator==.
-#if defined(__clang__)
-#pragma clang loop vectorize_width(2)
-#endif
- for (size_t i = 0; i < sizeof(TMat22) / sizeof(uint64_t); i++) {
- result |= li[i] ^ ri[i];
- }
- return result != 0;
- }
-
template
static constexpr TMat22 translation(const TVec2& t) noexcept {
TMat22 r;
diff --git a/libs/math/include/math/mat4.h b/libs/math/include/math/mat4.h
index fa5301adfaa..d44081b2648 100644
--- a/libs/math/include/math/mat4.h
+++ b/libs/math/include/math/mat4.h
@@ -272,24 +272,6 @@ class MATH_EMPTY_BASES TMat44 :
template
constexpr TMat44(const TMat33& matrix, const TVec4& column3) noexcept;
- /*
- * helpers
- */
-
- // returns false if the two matrices are different. May return false if they're the
- // same, with some elements only differing by +0 or -0. Behaviour is undefined with NaNs.
- static constexpr bool fuzzyEqual(TMat44 const& l, TMat44 const& r) noexcept {
- uint64_t const* const li = reinterpret_cast(&l);
- uint64_t const* const ri = reinterpret_cast(&r);
- uint64_t result = 0;
- // For some reason clang is not able to vectorize this loop when the number of iteration
- // is known and constant (!?!?!). Still this is better than operator==.
- for (size_t i = 0; i < sizeof(TMat44) / sizeof(uint64_t); i++) {
- result |= li[i] ^ ri[i];
- }
- return result != 0;
- }
-
static constexpr TMat44 ortho(T left, T right, T bottom, T top, T near, T far) noexcept;
static constexpr TMat44 frustum(T left, T right, T bottom, T top, T near, T far) noexcept;
diff --git a/libs/math/tests/test_mat.cpp b/libs/math/tests/test_mat.cpp
index 6afe9c81fc3..d53e73a9168 100644
--- a/libs/math/tests/test_mat.cpp
+++ b/libs/math/tests/test_mat.cpp
@@ -573,6 +573,18 @@ do { \
} \
} while(0)
+//------------------------------------------------------------------------------
+// A macro to help with vector comparisons within a range.
+#define EXPECT_VEC_NEAR(VEC1, VEC2, eps) \
+do { \
+ const decltype(VEC1) v1 = VEC1; \
+ const decltype(VEC2) v2 = VEC2; \
+ for (int i = 0; i < v1.size(); ++i) { \
+ EXPECT_NEAR(v1[i], v2[i], eps); \
+ } \
+} while(0)
+
+
//------------------------------------------------------------------------------
// A macro to help with type comparisons within floating point range.
#define ASSERT_TYPE_EQ(T1, T2) \
@@ -834,9 +846,10 @@ TYPED_TEST(MatTestT, cofactor) {
M33T r = M33T::eulerZYX(rand_gen(), rand_gen(), rand_gen());
M33T c0 = details::matrix::cofactor(r);
M33T c1 = details::matrix::fastCofactor3(r);
- EXPECT_VEC_EQ(c0[0], c1[0]);
- EXPECT_VEC_EQ(c0[1], c1[1]);
- EXPECT_VEC_EQ(c0[2], c1[2]);
+
+ EXPECT_VEC_NEAR(c0[0], c1[0], value_eps);
+ EXPECT_VEC_NEAR(c0[1], c1[1], value_eps);
+ EXPECT_VEC_NEAR(c0[2], c1[2], value_eps);
}
}
diff --git a/web/filament-js/package.json b/web/filament-js/package.json
index 9dfa743cf1c..3bb5eaa82c1 100644
--- a/web/filament-js/package.json
+++ b/web/filament-js/package.json
@@ -1,6 +1,6 @@
{
"name": "filament",
- "version": "1.42.0",
+ "version": "1.42.1",
"description": "Real-time physically based rendering engine",
"main": "filament.js",
"module": "filament.js",