diff --git a/CMakeLists.txt b/CMakeLists.txt index dd5bfaaf8d6..33c2bb29685 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,9 @@ set(FILAMENT_METAL_HANDLE_ARENA_SIZE_IN_MB "8" CACHE STRING "Size of the Metal handle arena, default 8." ) +# Enable exceptions by default in spirv-cross. +set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS OFF) + # ================================================================================================== # CMake policies # ================================================================================================== @@ -339,6 +342,7 @@ endif() if (CYGWIN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti") + set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS ON) endif() if (MSVC) @@ -375,6 +379,7 @@ endif() # saved by -fno-exception and 10 KiB saved by -fno-rtti). if (ANDROID OR IOS OR WEBGL) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-exceptions -fno-rtti") + set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS ON) if (ANDROID OR WEBGL) # Omitting unwind info prevents the generation of readable stack traces in crash reports on iOS @@ -386,6 +391,7 @@ endif() # std::visit, which is not supported on iOS 11.0 when exceptions are enabled. if (IOS) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-exceptions") + set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS ON) endif() # With WebGL, we disable RTTI even for debug builds because we pass emscripten::val back and forth @@ -525,6 +531,11 @@ if (FILAMENT_SUPPORTS_METAL) set(MATC_API_FLAGS ${MATC_API_FLAGS} -a metal) endif() +# Disable ESSL 1.0 code generation. +if (NOT FILAMENT_ENABLE_FEATURE_LEVEL_0) + set(MATC_API_FLAGS ${MATC_API_FLAGS} -1) +endif() + # Enable debug info (preserves names in SPIR-V) if (FILAMENT_ENABLE_MATDBG) set(MATC_OPT_FLAGS ${MATC_OPT_FLAGS} -d) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index c3161a7700b..d000a526635 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -8,3 +8,5 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut +- matc: New option `-1` to disable generation of ESSL 1.0 code in Feature Level 0 materials +- matc: Support optimizations for ESSL 1.0 code [⚠️ **Recompile materials**] diff --git a/README.md b/README.md index 3090b1fd8a1..b0278db43f3 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.46.0' + implementation 'com.google.android.filament:filament-android:1.47.0' } ``` @@ -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: ```shell -pod 'Filament', '~> 1.46.0' +pod 'Filament', '~> 1.47.0' ``` ### Snapshots diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 62f148bf272..a87c44268c7 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,6 +7,10 @@ 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.47.0 +- engine: Support up to 4 side-by-side stereoscopic eyes, configurable at Engine creation time. See + `Engine::Config::stereoscopicEyeCount`. [⚠️ **Recompile Materials**] + ## v1.46.0 - engine: Allow instantiating Engine at a given feature level via `Engine::Builder::featureLevel` diff --git a/android/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index 72b499e618e..b5fd507aa18 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -384,6 +384,13 @@ Java_com_google_android_filament_Engine_nFlushAndWait(JNIEnv*, jclass, engine->flushAndWait(); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_Engine_nFlush(JNIEnv*, jclass, + jlong nativeEngine) { + Engine* engine = (Engine*) nativeEngine; + engine->flush(); +} + // Managers... extern "C" JNIEXPORT jlong JNICALL diff --git a/android/filament-android/src/main/cpp/Material.cpp b/android/filament-android/src/main/cpp/Material.cpp index b6865cfaaf2..7ef3b0b5be2 100644 --- a/android/filament-android/src/main/cpp/Material.cpp +++ b/android/filament-android/src/main/cpp/Material.cpp @@ -19,6 +19,7 @@ #include #include "common/NioUtils.h" +#include "common/CallbackUtils.h" using namespace filament; @@ -271,3 +272,17 @@ Java_com_google_android_filament_Material_nHasParameter(JNIEnv* env, jclass, env->ReleaseStringUTFChars(name_, name); return (jboolean) hasParameter; } + +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_Material_nCompile(JNIEnv *env, jclass clazz, + jlong nativeMaterial, jint priority, jint variants, jobject handler, jobject runnable) { + Material* material = (Material*) nativeMaterial; + JniCallback* jniCallback = JniCallback::make(env, handler, runnable); + material->compile( + (Material::CompilerPriorityQueue) priority, + (UserVariantFilterBit) variants, + jniCallback->getHandler(), [jniCallback](Material*){ + JniCallback::postToJavaAndDestroy(jniCallback); + }); +} diff --git a/android/filament-android/src/main/cpp/Scene.cpp b/android/filament-android/src/main/cpp/Scene.cpp index b1d3d003489..14b0b49a948 100644 --- a/android/filament-android/src/main/cpp/Scene.cpp +++ b/android/filament-android/src/main/cpp/Scene.cpp @@ -98,3 +98,22 @@ Java_com_google_android_filament_Scene_nHasEntity(JNIEnv *env, jclass type, jlon Entity entity = Entity::import(entityId); return (jboolean) scene->hasEntity(entity); } + +extern "C" +JNIEXPORT jboolean JNICALL +Java_com_google_android_filament_Scene_nGetEntities(JNIEnv *env, jclass , + jlong nativeScene, jintArray outArray, jint length) { + Scene const* const scene = (Scene*) nativeScene; + if (length < scene->getEntityCount()) { + // should not happen because we already checked on the java side + return JNI_FALSE; + } + jint *out = (jint *) env->GetIntArrayElements(outArray, nullptr); + scene->forEach([out, length, i = 0](Entity entity)mutable { + if (i < length) { // this is just paranoia here + out[i++] = (jint) entity.getId(); + } + }); + env->ReleaseIntArrayElements(outArray, (jint*) out, JNI_ABORT); + return JNI_TRUE; +} diff --git a/android/filament-android/src/main/java/com/google/android/filament/Engine.java b/android/filament-android/src/main/java/com/google/android/filament/Engine.java index 3f0130c3835..26fd9e81c3a 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Engine.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Engine.java @@ -150,8 +150,10 @@ public enum FeatureLevel { FEATURE_LEVEL_0, /** OpenGL ES 3.0 features (default) */ FEATURE_LEVEL_1, + /** OpenGL ES 3.1 features + 16 textures units + cubemap arrays */ + FEATURE_LEVEL_2, /** OpenGL ES 3.1 features + 31 textures units + cubemap arrays */ - FEATURE_LEVEL_2 + FEATURE_LEVEL_3, }; /** @@ -1077,6 +1079,18 @@ public void flushAndWait() { nFlushAndWait(getNativeObject()); } + /** + * Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) but does not wait + * for commands to be either executed or the hardware finished. + * + *

This is typically used after creating a lot of objects to start draining the command + * queue which has a limited size.

+ */ + public void flush() { + nFlush(getNativeObject()); + } + + @UsedByReflection("TextureHelper.java") public long getNativeObject() { if (mNativeObject == 0) { @@ -1149,6 +1163,7 @@ private static void assertDestroy(boolean success) { private static native boolean nIsValidSwapChain(long nativeEngine, long nativeSwapChain); private static native void nDestroyEntity(long nativeEngine, int entity); private static native void nFlushAndWait(long nativeEngine); + private static native void nFlush(long nativeEngine); private static native long nGetTransformManager(long nativeEngine); private static native long nGetLightManager(long nativeEngine); private static native long nGetRenderableManager(long nativeEngine); diff --git a/android/filament-android/src/main/java/com/google/android/filament/Material.java b/android/filament-android/src/main/java/com/google/android/filament/Material.java index 8f0d68a55cc..80b143ee80d 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Material.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Material.java @@ -18,6 +18,7 @@ import androidx.annotation.IntRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.Size; import com.google.android.filament.proguard.UsedByNative; @@ -238,6 +239,31 @@ public enum CullingMode { FRONT_AND_BACK } + public enum CompilerPriorityQueue { + HIGH, + LOW + } + + public static class UserVariantFilterBit { + /** Directional lighting */ + public static int DIRECTIONAL_LIGHTING = 0x01; + /** Dynamic lighting */ + public static int DYNAMIC_LIGHTING = 0x02; + /** Shadow receiver */ + public static int SHADOW_RECEIVER = 0x04; + /** Skinning */ + public static int SKINNING = 0x08; + /** Fog */ + public static int FOG = 0x10; + /** Variance shadow maps */ + public static int VSM = 0x20; + /** Screen-space reflections */ + public static int SSR = 0x40; + /** Instanced stereo rendering */ + public static int STE = 0x80; + public static int ALL = 0xFF; + } + @UsedByNative("Material.cpp") public static class Parameter { private static final Type[] sTypeValues = Type.values(); @@ -352,6 +378,55 @@ public Material build(@NonNull Engine engine) { } } + + /** + * Asynchronously ensures that a subset of this Material's variants are compiled. After issuing + * several compile() calls in a row, it is recommended to call {@link Engine#flush} + * such that the backend can start the compilation work as soon as possible. + * The provided callback is guaranteed to be called on the main thread after all specified + * variants of the material are compiled. This can take hundreds of milliseconds. + *

+ * If all the material's variants are already compiled, the callback will be scheduled as + * soon as possible, but this might take a few dozen millisecond, corresponding to how + * many previous frames are enqueued in the backend. This also varies by backend. Therefore, + * it is recommended to only call this method once per material shortly after creation. + *

+ *

+ * If the same variant is scheduled for compilation multiple times, the first scheduling + * takes precedence; later scheduling are ignored. + *

+ *

+ * caveat: A consequence is that if a variant is scheduled on the low priority queue and later + * scheduled again on the high priority queue, the later scheduling is ignored. + * Therefore, the second callback could be called before the variant is compiled. + * However, the first callback, if specified, will trigger as expected. + *

+ *

+ * The callback is guaranteed to be called. If the engine is destroyed while some material + * variants are still compiling or in the queue, these will be discarded and the corresponding + * callback will be called. In that case however the Material pointer passed to the callback + * is guaranteed to be invalid (either because it's been destroyed by the user already, or, + * because it's been cleaned-up by the Engine). + *

+ *

+ * {@link UserVariantFilterBit#ALL} should be used with caution. Only variants that an application + * needs should be included in the variants argument. For example, the STE variant is only used + * for stereoscopic rendering. If an application is not planning to render in stereo, this bit + * should be turned off to avoid unnecessary material compilations. + *

+ * @param priority Which priority queue to use, LOW or HIGH. + * @param variants Variants to include to the compile command. + * @param handler An {@link java.util.concurrent.Executor Executor}. On Android this can also be a {@link android.os.Handler Handler}. + * @param callback callback called on the main thread when the compilation is done on + * by backend. + */ + public void compile(@NonNull CompilerPriorityQueue priority, + int variants, + @Nullable Object handler, + @Nullable Runnable callback) { + nCompile(getNativeObject(), priority.ordinal(), variants, handler, callback); + } + /** * Creates a new instance of this material. Material instances should be freed using * {@link Engine#destroyMaterialInstance(MaterialInstance)}. @@ -953,6 +1028,7 @@ void clearNativeObject() { private static native long nCreateInstanceWithName(long nativeMaterial, @NonNull String name); private static native long nGetDefaultInstance(long nativeMaterial); + private static native void nCompile(long nativeMaterial, int priority, int variants, Object handler, Runnable runnable); private static native String nGetName(long nativeMaterial); private static native int nGetShading(long nativeMaterial); private static native int nGetInterpolation(long nativeMaterial); diff --git a/android/filament-android/src/main/java/com/google/android/filament/Scene.java b/android/filament-android/src/main/java/com/google/android/filament/Scene.java index fd425fbeaac..9a41a4f64e5 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Scene.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Scene.java @@ -16,6 +16,7 @@ package com.google.android.filament; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** @@ -190,6 +191,52 @@ public long getNativeObject() { return mNativeObject; } + /** + * Returns the list of all entities in the Scene. If outArray is provided and large enough, + * it is used to store the list and returned, otherwise a new array is allocated and returned. + * @param outArray an array to store the list of entities in the scene. + * @return outArray if it was used or a newly allocated array. + * @see #getEntityCount + */ + public int[] getEntities(@Nullable int[] outArray) { + int c = getEntityCount(); + if (outArray == null || outArray.length < c) { + outArray = new int[c]; + } + boolean success = nGetEntities(getNativeObject(), outArray, outArray.length); + if (!success) { + throw new IllegalStateException("Error retriving Scene's entities"); + } + return outArray; + } + + /** + * Returns the list of all entities in the Scene in a newly allocated array. + * @return an array containing the list of all entities in the scene. + * @see #getEntityCount + */ + public int[] getEntities() { + return getEntities(null); + } + + public interface EntityProcessor { + void process(@Entity int entity); + } + + /** + * Invokes user functor on each entity in the scene. + * + * It is not allowed to add or remove an entity from the scene within the functor. + * + * @param entityProcessor User provided functor called for each entity in the scene + */ + public void forEach(@NonNull EntityProcessor entityProcessor) { + int[] entities = getEntities(null); + for (int entity : entities) { + entityProcessor.process(entity); + } + } + void clearNativeObject() { mNativeObject = 0; } @@ -204,4 +251,5 @@ void clearNativeObject() { private static native int nGetRenderableCount(long nativeScene); private static native int nGetLightCount(long nativeScene); private static native boolean nHasEntity(long nativeScene, int entity); + private static native boolean nGetEntities(long nativeScene, int[] outArray, int length); } diff --git a/android/gradle.properties b/android/gradle.properties index d4b8dc0c94f..6dda5b45e97 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.46.0 +VERSION_NAME=1.47.0 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt b/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt index 372cba57004..102c4944e6e 100644 --- a/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt +++ b/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt @@ -26,6 +26,7 @@ import android.widget.TextView import android.widget.Toast import com.google.android.filament.Fence import com.google.android.filament.IndirectLight +import com.google.android.filament.Material import com.google.android.filament.Skybox import com.google.android.filament.View import com.google.android.filament.View.OnPickCallback @@ -392,6 +393,36 @@ class MainActivity : Activity() { Log.i(TAG, "The Filament backend took $total ms to load the model geometry.") modelViewer.engine.destroyFence(it) loadStartFence = null + + val materials = mutableSetOf() + val rcm = modelViewer.engine.renderableManager + modelViewer.scene.forEach { + val entity = it + if (rcm.hasComponent(entity)) { + val ri = rcm.getInstance(entity) + val c = rcm.getPrimitiveCount(ri) + for (i in 0 until c) { + val mi = rcm.getMaterialInstanceAt(ri, i) + val ma = mi.material + materials.add(ma) + } + } + } + materials.forEach { + it.compile( + Material.CompilerPriorityQueue.HIGH, + Material.UserVariantFilterBit.DIRECTIONAL_LIGHTING or + Material.UserVariantFilterBit.DYNAMIC_LIGHTING or + Material.UserVariantFilterBit.SHADOW_RECEIVER, + null, null) + it.compile( + Material.CompilerPriorityQueue.LOW, + Material.UserVariantFilterBit.FOG or + Material.UserVariantFilterBit.SKINNING or + Material.UserVariantFilterBit.SSR or + Material.UserVariantFilterBit.VSM, + null, null) + } } } diff --git a/android/samples/sample-hello-triangle/src/main/java/com/google/android/filament/hellotriangle/MainActivity.kt b/android/samples/sample-hello-triangle/src/main/java/com/google/android/filament/hellotriangle/MainActivity.kt index 8571ee4d60a..f4b1e868178 100644 --- a/android/samples/sample-hello-triangle/src/main/java/com/google/android/filament/hellotriangle/MainActivity.kt +++ b/android/samples/sample-hello-triangle/src/main/java/com/google/android/filament/hellotriangle/MainActivity.kt @@ -20,6 +20,8 @@ import android.animation.ValueAnimator import android.app.Activity import android.opengl.Matrix import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.view.Choreographer import android.view.Surface import android.view.SurfaceView @@ -157,6 +159,14 @@ class MainActivity : Activity() { private fun loadMaterial() { readUncompressedAsset("materials/baked_color.filamat").let { material = Material.Builder().payload(it, it.remaining()).build(engine) + material.compile( + Material.CompilerPriorityQueue.HIGH, + Material.UserVariantFilterBit.ALL, + Handler(Looper.getMainLooper())) { + android.util.Log.i("hellotriangle", + "Material " + material.name + " compiled.") + } + engine.flush() } } diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index e6685c7d5ef..f9bba52968f 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -242,6 +242,7 @@ set(MATERIAL_SRCS src/materials/antiAliasing/fxaa.mat src/materials/antiAliasing/taa.mat src/materials/vsmMipmap.mat + src/materials/blitLow.mat ) set(MATERIAL_FL0_SRCS @@ -249,13 +250,6 @@ set(MATERIAL_FL0_SRCS src/materials/skybox0.mat ) -# TODO(exv): Replace the below duplicated materials with a command-line option -# to matc to force disable including ESSL 1.0 code in FL0 materials. - -set(MATERIAL_SWITCH_FL0_SRCS_HACK - src/materials/blitLow.mat -) - # Embed the binary resource blob for materials. get_resgen_vars(${RESOURCE_DIR} materials) list(APPEND PRIVATE_HDRS ${RESGEN_HEADER}) @@ -326,30 +320,6 @@ foreach (mat_src ${MATERIAL_SRCS}) list(APPEND MATERIAL_BINS ${output_path}) endforeach() - -# HACK: Pick between the normal and FL0-exclusionary variants of the materials -# based on the value of FILAMENT_ENABLE_FEATURE_LEVEL_0. -foreach (mat_src ${MATERIAL_SWITCH_FL0_SRCS_HACK}) - get_filename_component(localname "${mat_src}" NAME_WE) - get_filename_component(fullname "${mat_src}" ABSOLUTE) - set(output_path "${MATERIAL_DIR}/${localname}.filamat") - - if (NOT FILAMENT_ENABLE_FATURE_LEVEL_0) - # Pick the non-FL0 variant instead. - string(REGEX REPLACE "\\.mat$" "1.mat" fullname "${fullname}") - string(REGEX REPLACE "\\.mat$" "1.mat" mat_src "${mat_src}") - endif() - - add_custom_command( - OUTPUT ${output_path} - COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname} - MAIN_DEPENDENCY ${fullname} - DEPENDS matc - COMMENT "Compiling material ${mat_src} to ${output_path}" - ) - list(APPEND MATERIAL_BINS ${output_path}) -endforeach() - if (FILAMENT_ENABLE_FEATURE_LEVEL_0 AND FILAMENT_SUPPORTS_OPENGL) foreach (mat_src ${MATERIAL_FL0_SRCS}) get_filename_component(localname "${mat_src}" NAME_WE) diff --git a/filament/backend/include/backend/BufferDescriptor.h b/filament/backend/include/backend/BufferDescriptor.h index 80fe182abb2..0d3490afb7a 100644 --- a/filament/backend/include/backend/BufferDescriptor.h +++ b/filament/backend/include/backend/BufferDescriptor.h @@ -113,7 +113,7 @@ class UTILS_PUBLIC BufferDescriptor { /** * Helper to create a BufferDescriptor that uses a KNOWN method pointer w/ object passed * by pointer as the callback. e.g.: - * auto bd = BufferDescriptor::make(buffer, size, &Foo::method, foo); + * auto bd = BufferDescriptor::make(buffer, size, foo); * * @param buffer Memory address of the CPU buffer to reference * @param size Size of the CPU buffer in bytes @@ -121,12 +121,12 @@ class UTILS_PUBLIC BufferDescriptor { * @return a new BufferDescriptor */ template - static BufferDescriptor make( - void const* buffer, size_t size, T* data, CallbackHandler* handler = nullptr) noexcept { + static BufferDescriptor make(void const* buffer, size_t size, T* data, + CallbackHandler* handler = nullptr) noexcept { return { buffer, size, handler, [](void* b, size_t s, void* u) { - (*static_cast(u)->*method)(b, s); + (static_cast(u)->*method)(b, s); }, data }; } @@ -145,14 +145,14 @@ class UTILS_PUBLIC BufferDescriptor { * @return a new BufferDescriptor */ template - static BufferDescriptor make( - void const* buffer, size_t size, T&& functor, CallbackHandler* handler = nullptr) noexcept { + static BufferDescriptor make(void const* buffer, size_t size, T&& functor, + CallbackHandler* handler = nullptr) noexcept { return { buffer, size, handler, [](void* b, size_t s, void* u) { - T& that = *static_cast(u); - that(b, s); - delete &that; + T* const that = static_cast(u); + that->operator()(b, s); + delete that; }, new T(std::forward(functor)) }; @@ -201,7 +201,7 @@ class UTILS_PUBLIC BufferDescriptor { return mUser; } - //! CPU mempry-buffer virtual address + //! CPU memory-buffer virtual address void* buffer = nullptr; //! CPU memory-buffer size in bytes diff --git a/filament/backend/include/backend/PixelBufferDescriptor.h b/filament/backend/include/backend/PixelBufferDescriptor.h index 1b498032fdc..67564fcce46 100644 --- a/filament/backend/include/backend/PixelBufferDescriptor.h +++ b/filament/backend/include/backend/PixelBufferDescriptor.h @@ -141,7 +141,7 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { CallbackHandler* handler = nullptr) noexcept { return { buffer, size, format, type, alignment, left, top, stride, handler, [](void* b, size_t s, void* u) { - (*static_cast(u)->*method)(b, s); }, data }; + (static_cast(u)->*method)(b, s); }, data }; } template @@ -149,7 +149,7 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { PixelDataFormat format, PixelDataType type, T* data, CallbackHandler* handler = nullptr) noexcept { return { buffer, size, format, type, handler, [](void* b, size_t s, void* u) { - (*static_cast(u)->*method)(b, s); }, data }; + (static_cast(u)->*method)(b, s); }, data }; } template @@ -157,7 +157,7 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { backend::CompressedPixelDataType format, uint32_t imageSize, T* data, CallbackHandler* handler = nullptr) noexcept { return { buffer, size, format, imageSize, handler, [](void* b, size_t s, void* u) { - (*static_cast(u)->*method)(b, s); }, data + (static_cast(u)->*method)(b, s); }, data }; } @@ -168,9 +168,9 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { CallbackHandler* handler = nullptr) noexcept { return { buffer, size, format, type, alignment, left, top, stride, handler, [](void* b, size_t s, void* u) { - T& that = *static_cast(u); - that(b, s); - delete &that; + T* const that = static_cast(u); + that->operator()(b, s); + delete that; }, new T(std::forward(functor)) }; } @@ -181,9 +181,9 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { CallbackHandler* handler = nullptr) noexcept { return { buffer, size, format, type, handler, [](void* b, size_t s, void* u) { - T& that = *static_cast(u); - that(b, s); - delete &that; + T* const that = static_cast(u); + that->operator()(b, s); + delete that; }, new T(std::forward(functor)) }; } @@ -194,9 +194,9 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { CallbackHandler* handler = nullptr) noexcept { return { buffer, size, format, imageSize, handler, [](void* b, size_t s, void* u) { - T& that = *static_cast(u); - that(b, s); - delete &that; + T* const that = static_cast(u); + that->operator()(b, s); + delete that; }, new T(std::forward(functor)) }; } diff --git a/filament/backend/src/metal/MetalShaderCompiler.mm b/filament/backend/src/metal/MetalShaderCompiler.mm index e798d1b5ae3..7f4280979ab 100644 --- a/filament/backend/src/metal/MetalShaderCompiler.mm +++ b/filament/backend/src/metal/MetalShaderCompiler.mm @@ -170,9 +170,6 @@ bool isReady() const noexcept { CompilerPriorityQueue const priorityQueue = program.getPriorityQueue(); mCompilerThreadPool.queue(priorityQueue, token, [this, name, device = mDevice, program = std::move(program), token]() { - int sleepTime = atoi(name.c_str()); - sleep(sleepTime); - MetalFunctionBundle compiledProgram = compileProgram(program, device); token->set(compiledProgram); diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index b183cd91d57..71305fffe33 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -284,6 +284,7 @@ void OpenGLContext::setDefaultState() noexcept { if (ext.EXT_clip_cull_distance) { glEnable(GL_CLIP_DISTANCE0); + glEnable(GL_CLIP_DISTANCE1); } } diff --git a/filament/backend/src/opengl/gl_headers.h b/filament/backend/src/opengl/gl_headers.h index c934cb7d837..2491e742f08 100644 --- a/filament/backend/src/opengl/gl_headers.h +++ b/filament/backend/src/opengl/gl_headers.h @@ -190,8 +190,10 @@ using namespace glext; #if defined(GL_EXT_clip_cull_distance) # define GL_CLIP_DISTANCE0 GL_CLIP_DISTANCE0_EXT +# define GL_CLIP_DISTANCE1 GL_CLIP_DISTANCE1_EXT #else # define GL_CLIP_DISTANCE0 0x3000 +# define GL_CLIP_DISTANCE1 0x3001 #endif #if defined(GL_KHR_debug) diff --git a/filament/include/filament/Camera.h b/filament/include/filament/Camera.h index ab06a413b58..2d8e6310fc9 100644 --- a/filament/include/filament/Camera.h +++ b/filament/include/filament/Camera.h @@ -286,17 +286,20 @@ class UTILS_PUBLIC Camera : public FilamentAPI { /** Sets a custom projection matrix for each eye. * * The projectionForCulling, near, and far parameters establish a "culling frustum" which must - * encompass anything either eye can see. + * encompass anything any eye can see. All projection matrices must be set simultaneously. The + * number of stereoscopic eyes is controlled by the stereoscopicEyeCount setting inside of + * Engine::Config. * - * @param projection an array of projection matrices, only the first - * CONFIG_STEREOSCOPIC_EYES (2) are read + * @param projection an array of projection matrices, only the first config.stereoscopicEyeCount + * are read * @param count size of the projection matrix array to set, must be - * >= CONFIG_STEREOSCOPIC_EYES (2) + * >= config.stereoscopicEyeCount * @param projectionForCulling custom projection matrix for culling, must encompass both eyes * @param near distance in world units from the camera to the culling near plane. \p near > 0. * @param far distance in world units from the camera to the culling far plane. \p far > \p * near. * @see setCustomProjection + * @see Engine::Config::stereoscopicEyeCount */ void setCustomEyeProjection(math::mat4 const* projection, size_t count, math::mat4 const& projectionForCulling, double near, double far); @@ -357,8 +360,8 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * The projection matrix used for rendering always has its far plane set to infinity. This * is why it may differ from the matrix set through setProjection() or setLensProjection(). * - * @param eyeId the index of the eye to return the projection matrix for, must be < - * CONFIG_STEREOSCOPIC_EYES (2) + * @param eyeId the index of the eye to return the projection matrix for, must be + * < config.stereoscopicEyeCount * @return The projection matrix used for rendering * * @see setProjection, setLensProjection, setCustomProjection, getCullingProjectionMatrix, @@ -416,7 +419,7 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * This method is not intended to be called every frame. Instead, to update the position of the * head, use Camera::setModelMatrix. * - * @param eyeId the index of the eye to set, must be < CONFIG_STEREOSCOPIC_EYES (2) + * @param eyeId the index of the eye to set, must be < config.stereoscopicEyeCount * @param model the model matrix for an individual eye */ void setEyeModelMatrix(uint8_t eyeId, math::mat4 const& model); diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index 2ab2e20d265..2442e6c2554 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -291,6 +291,14 @@ class UTILS_PUBLIC Engine { * Currently only respected by the Metal backend. */ size_t textureUseAfterFreePoolSize = 0; + + /* + * The number of eyes to render when stereoscopic rendering is enabled. Supported values are + * between 1 and CONFIG_MAX_STEREOSCOPIC_EYES (inclusive). + * + * @see View::setStereoscopicOptions + */ + uint8_t stereoscopicEyeCount = 2; }; diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h index 5eddb99143b..a58587e9ede 100644 --- a/filament/include/filament/View.h +++ b/filament/include/filament/View.h @@ -763,10 +763,10 @@ class UTILS_PUBLIC View : public FilamentAPI { * @param handler Handler to dispatch the callback or nullptr for the default handler. */ template - void pick(uint32_t x, uint32_t y, T* instance, backend::CallbackHandler* handler = nullptr) noexcept { + void pick(uint32_t x, uint32_t y, T* instance, + backend::CallbackHandler* handler = nullptr) noexcept { PickingQuery& query = pick(x, y, [](PickingQueryResult const& result, PickingQuery* pq) { - void* user = pq->storage; - (*static_cast(user)->*method)(result); + (static_cast(pq->storage[0])->*method)(result); }, handler); query.storage[0] = instance; } @@ -783,11 +783,11 @@ class UTILS_PUBLIC View : public FilamentAPI { * @param handler Handler to dispatch the callback or nullptr for the default handler. */ template - void pick(uint32_t x, uint32_t y, T instance, backend::CallbackHandler* handler = nullptr) noexcept { + void pick(uint32_t x, uint32_t y, T instance, + backend::CallbackHandler* handler = nullptr) noexcept { static_assert(sizeof(instance) <= sizeof(PickingQuery::storage), "user data too large"); PickingQuery& query = pick(x, y, [](PickingQueryResult const& result, PickingQuery* pq) { - void* user = pq->storage; - T* that = static_cast(user); + T* const that = static_cast(reinterpret_cast(pq->storage)); (that->*method)(result); that->~T(); }, handler); @@ -804,15 +804,15 @@ class UTILS_PUBLIC View : public FilamentAPI { * @param handler Handler to dispatch the callback or nullptr for the default handler. */ template - void pick(uint32_t x, uint32_t y, T functor, backend::CallbackHandler* handler = nullptr) noexcept { + void pick(uint32_t x, uint32_t y, T functor, + backend::CallbackHandler* handler = nullptr) noexcept { static_assert(sizeof(functor) <= sizeof(PickingQuery::storage), "functor too large"); PickingQuery& query = pick(x, y, handler, (PickingQueryResultCallback)[](PickingQueryResult const& result, PickingQuery* pq) { - void* user = pq->storage; - T& that = *static_cast(user); - that(result); - that.~T(); - }); + T* const that = static_cast(reinterpret_cast(pq->storage)); + that->operator()(result); + that->~T(); + }); new(query.storage) T(std::move(functor)); } diff --git a/filament/src/PerViewUniforms.cpp b/filament/src/PerViewUniforms.cpp index 07a2fa8202a..03d1898567e 100644 --- a/filament/src/PerViewUniforms.cpp +++ b/filament/src/PerViewUniforms.cpp @@ -82,7 +82,8 @@ void PerViewUniforms::prepareCamera(FEngine& engine, const CameraInfo& camera) n s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn); mat4f const& headFromWorld = camera.view; - for (uint8_t i = 0; i < CONFIG_STEREOSCOPIC_EYES; i++) { + Engine::Config const& config = engine.getConfig(); + for (int i = 0; i < config.stereoscopicEyeCount; i++) { mat4f const& eyeFromHead = camera.eyeFromView[i]; // identity for monoscopic rendering mat4f const& clipFromEye = camera.eyeProjection[i]; // clipFromEye * eyeFromHead * headFromWorld diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index 55a66a75d30..2932fcf481b 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -116,14 +116,17 @@ void RenderPass::appendCommands(FEngine& engine, CommandTypeFlags const commandT commandCount += 1; // for the sentinel Command* const curr = append(commandCount); + auto stereoscopicEyeCount = + renderFlags & IS_STEREOSCOPIC ? engine.getConfig().stereoscopicEyeCount : 1; + const float3 cameraPosition(mCameraPosition); const float3 cameraForwardVector(mCameraForwardVector); auto work = [commandTypeFlags, curr, &soa, variant, renderFlags, visibilityMask, cameraPosition, - cameraForwardVector] + cameraForwardVector, stereoscopicEyeCount] (uint32_t startIndex, uint32_t indexCount) { RenderPass::generateCommands(commandTypeFlags, curr, soa, { startIndex, startIndex + indexCount }, variant, renderFlags, visibilityMask, - cameraPosition, cameraForwardVector); + cameraPosition, cameraForwardVector, stereoscopicEyeCount); }; if (vr.size() <= JOBS_PARALLEL_FOR_COMMANDS_COUNT) { @@ -374,8 +377,8 @@ UTILS_NOINLINE void RenderPass::generateCommands(uint32_t commandTypeFlags, Command* const commands, FScene::RenderableSoa const& soa, Range range, Variant variant, RenderFlags renderFlags, - FScene::VisibleMaskType visibilityMask, - float3 cameraPosition, float3 cameraForward) noexcept { + FScene::VisibleMaskType visibilityMask, float3 cameraPosition, float3 cameraForward, + uint8_t instancedStereoEyeCount) noexcept { SYSTRACE_CALL(); @@ -406,11 +409,13 @@ void RenderPass::generateCommands(uint32_t commandTypeFlags, Command* const comm switch (commandTypeFlags & (CommandTypeFlags::COLOR | CommandTypeFlags::DEPTH)) { case CommandTypeFlags::COLOR: curr = generateCommandsImpl(commandTypeFlags, curr, - soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward); + soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward, + instancedStereoEyeCount); break; case CommandTypeFlags::DEPTH: curr = generateCommandsImpl(commandTypeFlags, curr, - soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward); + soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward, + instancedStereoEyeCount); break; default: // we should never end-up here @@ -433,7 +438,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags, Command* UTILS_RESTRICT curr, FScene::RenderableSoa const& UTILS_RESTRICT soa, Range range, Variant const variant, RenderFlags renderFlags, FScene::VisibleMaskType visibilityMask, - float3 cameraPosition, float3 cameraForward) noexcept { + float3 cameraPosition, float3 cameraForward, uint8_t instancedStereoEyeCount) noexcept { // generateCommands() writes both the draw and depth commands simultaneously such that // we go throw the list of renderables just once. @@ -529,7 +534,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags, // eye count. if (UTILS_UNLIKELY(hasInstancedStereo)) { cmdColor.primitive.instanceCount = - (soaInstanceInfo[i].count * CONFIG_STEREOSCOPIC_EYES) | + (soaInstanceInfo[i].count * instancedStereoEyeCount) | PrimitiveInfo::USER_INSTANCE_MASK; } @@ -555,7 +560,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags, if (UTILS_UNLIKELY(hasInstancedStereo)) { cmdColor.primitive.instanceCount = - (soaInstanceInfo[i].count * CONFIG_STEREOSCOPIC_EYES) | + (soaInstanceInfo[i].count * instancedStereoEyeCount) | PrimitiveInfo::USER_INSTANCE_MASK; } } diff --git a/filament/src/RenderPass.h b/filament/src/RenderPass.h index 4b671a648f9..4474079594f 100644 --- a/filament/src/RenderPass.h +++ b/filament/src/RenderPass.h @@ -408,13 +408,15 @@ class RenderPass { FScene::RenderableSoa const& soa, utils::Range range, Variant variant, RenderFlags renderFlags, FScene::VisibleMaskType visibilityMask, - math::float3 cameraPosition, math::float3 cameraForward) noexcept; + math::float3 cameraPosition, math::float3 cameraForward, + uint8_t instancedStereoEyeCount) noexcept; template static inline Command* generateCommandsImpl(uint32_t extraFlags, Command* curr, FScene::RenderableSoa const& soa, utils::Range range, Variant variant, RenderFlags renderFlags, FScene::VisibleMaskType visibilityMask, - math::float3 cameraPosition, math::float3 cameraForward) noexcept; + math::float3 cameraPosition, math::float3 cameraForward, + uint8_t instancedStereoEyeCount) noexcept; static void setupColorCommand(Command& cmdDraw, Variant variant, FMaterialInstance const* mi, bool inverseFrontFaces) noexcept; diff --git a/filament/src/details/Camera.cpp b/filament/src/details/Camera.cpp index 88bdb246e85..81e54d27b51 100644 --- a/filament/src/details/Camera.cpp +++ b/filament/src/details/Camera.cpp @@ -96,11 +96,11 @@ void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& p, void UTILS_NOINLINE FCamera::setCustomEyeProjection(math::mat4 const* projection, size_t count, math::mat4 const& projectionForCulling, double near, double far) { - ASSERT_PRECONDITION(count >= CONFIG_STEREOSCOPIC_EYES, + const Engine::Config& config = mEngine.getConfig(); + ASSERT_PRECONDITION(count >= config.stereoscopicEyeCount, "All eye projections must be supplied together, count must be >= " - "CONFIG_STEREOSCOPIC_EYES(%d)", - CONFIG_STEREOSCOPIC_EYES); - for (uint8_t i = 0; i < CONFIG_STEREOSCOPIC_EYES; i++) { + "config.stereoscopicEyeCount (%d)", config.stereoscopicEyeCount); + for (int i = 0; i < config.stereoscopicEyeCount; i++) { mEyeProjection[i] = projection[i]; } mProjectionForCulling = projectionForCulling; @@ -165,7 +165,8 @@ void UTILS_NOINLINE FCamera::setProjection(Camera::Projection projection, } math::mat4 FCamera::getProjectionMatrix(uint8_t eye) const noexcept { - assert_invariant(eye < CONFIG_STEREOSCOPIC_EYES); + UTILS_UNUSED_IN_RELEASE const Engine::Config& config = mEngine.getConfig(); + assert_invariant(eye < config.stereoscopicEyeCount); // This is where we transform the user clip-space (GL convention) to our virtual clip-space // (inverted DX convention) // Note that this math ends up setting the projection matrix' p33 to 0, which is where we're @@ -190,6 +191,13 @@ math::mat4 FCamera::getCullingProjectionMatrix() const noexcept { return m * mProjectionForCulling; } +const math::mat4& FCamera::getUserProjectionMatrix(uint8_t eyeId) const { + const Engine::Config& config = mEngine.getConfig(); + ASSERT_PRECONDITION(eyeId < config.stereoscopicEyeCount, + "eyeId must be < config.stereoscopicEyeCount (%d)", config.stereoscopicEyeCount); + return mEyeProjection[eyeId]; +} + void UTILS_NOINLINE FCamera::setModelMatrix(const mat4f& modelMatrix) noexcept { FTransformManager& transformManager = mEngine.getTransformManager(); transformManager.setTransform(transformManager.getInstance(mEntity), modelMatrix); @@ -201,8 +209,9 @@ void UTILS_NOINLINE FCamera::setModelMatrix(const mat4& modelMatrix) noexcept { } void UTILS_NOINLINE FCamera::setEyeModelMatrix(uint8_t eyeId, math::mat4 const& model) { - ASSERT_PRECONDITION(eyeId < CONFIG_STEREOSCOPIC_EYES, - "eyeId must be < CONFIG_STEREOSCOPIC_EYES(%d)", CONFIG_STEREOSCOPIC_EYES); + const Engine::Config& config = mEngine.getConfig(); + ASSERT_PRECONDITION(eyeId < config.stereoscopicEyeCount, + "eyeId must be < config.stereoscopicEyeCount (%d)", config.stereoscopicEyeCount); mEyeFromView[eyeId] = inverse(model); } @@ -249,10 +258,15 @@ double FCamera::computeEffectiveFov(double fovInDegrees, double focusDistance) n return fov * math::d::RAD_TO_DEG; } +uint8_t FCamera::getStereoscopicEyeCount() const noexcept { + const Engine::Config& config = mEngine.getConfig(); + return config.stereoscopicEyeCount; +} + // ------------------------------------------------------------------------------------------------ CameraInfo::CameraInfo(FCamera const& camera) noexcept { - for (uint8_t i = 0; i < CONFIG_STEREOSCOPIC_EYES; i++) { + for (int i = 0; i < camera.getStereoscopicEyeCount(); i++) { eyeProjection[i] = mat4f{ camera.getProjectionMatrix(i) }; } cullingProjection = mat4f{ camera.getCullingProjectionMatrix() }; @@ -268,7 +282,7 @@ CameraInfo::CameraInfo(FCamera const& camera) noexcept { CameraInfo::CameraInfo(FCamera const& camera, math::mat4 const& inWorldTransform) noexcept { const mat4 modelMatrix{ inWorldTransform * camera.getModelMatrix() }; - for (uint8_t i = 0; i < CONFIG_STEREOSCOPIC_EYES; i++) { + for (int i = 0; i < camera.getStereoscopicEyeCount(); i++) { eyeProjection[i] = mat4f{ camera.getProjectionMatrix(i) }; eyeFromView[i] = mat4f{ camera.getEyeFromViewMatrix(i) }; } diff --git a/filament/src/details/Camera.h b/filament/src/details/Camera.h index bab9f11326a..1989178772e 100644 --- a/filament/src/details/Camera.h +++ b/filament/src/details/Camera.h @@ -85,11 +85,7 @@ class FCamera : public Camera { math::mat4 getEyeFromViewMatrix(uint8_t eye) const noexcept { return mEyeFromView[eye]; } // viewing projection matrix set by the user - const math::mat4& getUserProjectionMatrix(uint8_t eyeId) const { - ASSERT_PRECONDITION(eyeId < CONFIG_STEREOSCOPIC_EYES, - "eyeId must be < CONFIG_STEREOSCOPIC_EYES(%d)", CONFIG_STEREOSCOPIC_EYES); - return mEyeProjection[eyeId]; - } + const math::mat4& getUserProjectionMatrix(uint8_t eyeId) const; // culling projection matrix set by the user math::mat4 getUserCullingProjectionMatrix() const noexcept { return mProjectionForCulling; } @@ -189,6 +185,8 @@ class FCamera : public Camera { static double computeEffectiveFov(double fovInDegrees, double focusDistance) noexcept; + uint8_t getStereoscopicEyeCount() const noexcept; + utils::Entity getEntity() const noexcept { return mEntity; } @@ -204,10 +202,10 @@ class FCamera : public Camera { utils::Entity mEntity; // For monoscopic cameras, mEyeProjection[0] == mEyeProjection[1]. - math::mat4 mEyeProjection[CONFIG_STEREOSCOPIC_EYES]; // projection matrix per eye (infinite far) - math::mat4 mProjectionForCulling; // projection matrix (with far plane) - math::mat4 mEyeFromView[CONFIG_STEREOSCOPIC_EYES]; // transforms from the main view (head) - // space to each eye's unique view space + math::mat4 mEyeProjection[CONFIG_MAX_STEREOSCOPIC_EYES]; // projection matrix per eye (infinite far) + math::mat4 mProjectionForCulling; // projection matrix (with far plane) + math::mat4 mEyeFromView[CONFIG_MAX_STEREOSCOPIC_EYES]; // transforms from the main view (head) + // space to each eye's unique view space math::double2 mScalingCS = {1.0}; // additional scaling applied to projection math::double2 mShiftCS = {0.0}; // additional translation applied to projection @@ -232,15 +230,15 @@ struct CameraInfo { math::mat4f projection; // for stereo rendering, one matrix per eye - math::mat4f eyeProjection[CONFIG_STEREOSCOPIC_EYES] = {}; + math::mat4f eyeProjection[CONFIG_MAX_STEREOSCOPIC_EYES] = {}; }; - math::mat4f cullingProjection; // projection matrix for culling - math::mat4f model; // camera model matrix - math::mat4f view; // camera view matrix (inverse(model)) - math::mat4f eyeFromView[CONFIG_STEREOSCOPIC_EYES]; // eye view matrix (only for stereoscopic) - math::mat4 worldTransform; // world transform (already applied - // to model and view) + math::mat4f cullingProjection; // projection matrix for culling + math::mat4f model; // camera model matrix + math::mat4f view; // camera view matrix (inverse(model)) + math::mat4f eyeFromView[CONFIG_MAX_STEREOSCOPIC_EYES]; // eye view matrix (only for stereoscopic) + math::mat4 worldTransform; // world transform (already applied + // to model and view) math::float4 clipTransform{1, 1, 0, 0}; // clip-space transform, only for VERTEX_DOMAIN_DEVICE float zn{}; // distance (positive) to the near plane float zf{}; // distance (positive) to the far plane diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index 30c25ad8e09..5bb51b6902c 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -1273,6 +1273,9 @@ Engine::Config Engine::BuilderDetails::validateConfig(const Config* const pConfi // This value gets validated during driver creation, so pass it through config.driverHandleArenaSizeMB = config.driverHandleArenaSizeMB; + config.stereoscopicEyeCount = + std::clamp(config.stereoscopicEyeCount, uint8_t(1), CONFIG_MAX_STEREOSCOPIC_EYES); + return config; } diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index 03cd2f8605a..91539a6db1f 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -260,6 +260,9 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_POWER_VR_SHADER_WORKAROUNDS, (bool)powerVrShaderWorkarounds }); + mSpecializationConstants.push_back({ + +ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT, + (int)engine.getConfig().stereoscopicEyeCount }); if (UTILS_UNLIKELY(engine.getShaderLanguage() == ShaderLanguage::ESSL1)) { // The actual value of this spec-constant is set in the OpenGLDriver backend. mSpecializationConstants.push_back({ diff --git a/filament/src/fg/FrameGraph.h b/filament/src/fg/FrameGraph.h index 4538801bed6..803e5c8088c 100644 --- a/filament/src/fg/FrameGraph.h +++ b/filament/src/fg/FrameGraph.h @@ -527,7 +527,7 @@ class FrameGraph { template FrameGraphPass& FrameGraph::addPass(char const* name, Setup setup, Execute&& execute) { - static_assert(sizeof(Execute) < 1024, "Execute() lambda is capturing too much data."); + static_assert(sizeof(Execute) < 2048, "Execute() lambda is capturing too much data."); // create the FrameGraph pass auto* const pass = mArena.make>(std::forward(execute)); diff --git a/filament/src/materials/blitLow.mat b/filament/src/materials/blitLow.mat index 4bebc8612e6..09e6c79920c 100644 --- a/filament/src/materials/blitLow.mat +++ b/filament/src/materials/blitLow.mat @@ -35,7 +35,7 @@ vertex { fragment { void postProcess(inout PostProcessInputs postProcess) { -#if __VERSION__ == 100 +#if FILAMENT_EFFECTIVE_VERSION == 100 postProcess.color = texture2D(materialParams_color, variable_vertex.xy); #else postProcess.color = textureLod(materialParams_color, variable_vertex.xy, 0.0); diff --git a/filament/src/materials/blitLow1.mat b/filament/src/materials/blitLow1.mat deleted file mode 100644 index efe17433c53..00000000000 --- a/filament/src/materials/blitLow1.mat +++ /dev/null @@ -1,39 +0,0 @@ -material { - name : blitLow, - parameters : [ - { - type : sampler2d, - name : color, - precision: medium - }, - { - type : float4, - name : resolution, - precision: high - }, - { - type : float4, - name : viewport, - precision: high - } - ], - variables : [ - vertex - ], - depthWrite : false, - depthCulling : false, - domain: postprocess, -} - -vertex { - void postProcessVertex(inout PostProcessVertexInputs postProcess) { - postProcess.vertex.xy = materialParams.viewport.xy + postProcess.normalizedUV * materialParams.viewport.zw; - postProcess.vertex.xy = uvToRenderTargetUV(postProcess.vertex.xy); - } -} - -fragment { - void postProcess(inout PostProcessInputs postProcess) { - postProcess.color = textureLod(materialParams_color, variable_vertex.xy, 0.0); - } -} diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index 19007b27293..24db7659fb0 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.46.0" + spec.version = "1.47.0" 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.46.0/filament-v1.46.0-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.47.0/filament-v1.47.0-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/filabridge/include/filament/MaterialEnums.h b/libs/filabridge/include/filament/MaterialEnums.h index b6d677a4c94..67cdd8620f9 100644 --- a/libs/filabridge/include/filament/MaterialEnums.h +++ b/libs/filabridge/include/filament/MaterialEnums.h @@ -28,7 +28,7 @@ namespace filament { // update this when a new version of filament wouldn't work with older materials -static constexpr size_t MATERIAL_VERSION = 46; +static constexpr size_t MATERIAL_VERSION = 47; /** * Supported shading models diff --git a/libs/filabridge/include/private/filament/EngineEnums.h b/libs/filabridge/include/private/filament/EngineEnums.h index 70d736cb4d3..1b643735d50 100644 --- a/libs/filabridge/include/private/filament/EngineEnums.h +++ b/libs/filabridge/include/private/filament/EngineEnums.h @@ -66,6 +66,7 @@ enum class ReservedSpecializationConstants : uint8_t { CONFIG_POWER_VR_SHADER_WORKAROUNDS = 5, CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP = 6, CONFIG_DEBUG_FROXEL_VISUALIZATION = 7, + CONFIG_STEREO_EYE_COUNT = 8, }; // This value is limited by UBO size, ES3.0 only guarantees 16 KiB. @@ -75,7 +76,8 @@ constexpr size_t CONFIG_MAX_LIGHT_INDEX = CONFIG_MAX_LIGHT_COUNT - 1; // The number of specialization constants that Filament reserves for its own use. These are always // the first constants (from 0 to CONFIG_MAX_RESERVED_SPEC_CONSTANTS - 1). -constexpr size_t CONFIG_MAX_RESERVED_SPEC_CONSTANTS = 8; +// Updating this value necessitates a material version bump. +constexpr size_t CONFIG_MAX_RESERVED_SPEC_CONSTANTS = 16; // The maximum number of shadowmaps. // There is currently a maximum limit of 128 shadowmaps. @@ -120,8 +122,10 @@ constexpr size_t CONFIG_MAX_BONE_COUNT = 256; // Furthermore, this is constrained by CONFIG_MINSPEC_UBO_SIZE (16 bytes per morph target). constexpr size_t CONFIG_MAX_MORPH_TARGET_COUNT = 256; -// The number of eyes in stereoscopic mode. -constexpr uint8_t CONFIG_STEREOSCOPIC_EYES = 2; +// The max number of eyes supported in stereoscopic mode. +// The number of eyes actually rendered is set at Engine creation time, see +// Engine::Config::stereoscopicEyeCount. +constexpr uint8_t CONFIG_MAX_STEREOSCOPIC_EYES = 4; } // namespace filament diff --git a/libs/filabridge/include/private/filament/UibStructs.h b/libs/filabridge/include/private/filament/UibStructs.h index 1def148334e..dba88fc1d6a 100644 --- a/libs/filabridge/include/private/filament/UibStructs.h +++ b/libs/filabridge/include/private/filament/UibStructs.h @@ -76,7 +76,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init) math::mat4f worldFromViewMatrix; // clip view -> world : model matrix math::mat4f clipFromViewMatrix; // clip <- view world : projection matrix math::mat4f viewFromClipMatrix; // clip -> view world : inverse projection matrix - math::mat4f clipFromWorldMatrix[CONFIG_STEREOSCOPIC_EYES]; // clip <- view <- world + math::mat4f clipFromWorldMatrix[CONFIG_MAX_STEREOSCOPIC_EYES]; // clip <- view <- world math::mat4f worldFromClipMatrix; // clip -> view -> world math::mat4f userWorldFromWorldMatrix; // userWorld <- world math::float4 clipTransform; // [sx, sy, tx, ty] only used by VERTEX_DOMAIN_DEVICE @@ -203,7 +203,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init) float es2Reserved2; // bring PerViewUib to 2 KiB - math::float4 reserved[48]; + math::float4 reserved[40]; }; // 2 KiB == 128 float4s diff --git a/libs/filagui/CMakeLists.txt b/libs/filagui/CMakeLists.txt index cb1024005e9..66058c4103c 100644 --- a/libs/filagui/CMakeLists.txt +++ b/libs/filagui/CMakeLists.txt @@ -35,19 +35,11 @@ set(MATERIAL_SRCS file(MAKE_DIRECTORY ${MATERIAL_DIR}) -# HACK: Pick between the normal and FL0-exclusionary variants of the materials -# based on the value of FILAMENT_ENABLE_FEATURE_LEVEL_0. foreach (mat_src ${MATERIAL_SRCS}) get_filename_component(localname "${mat_src}" NAME_WE) get_filename_component(fullname "${mat_src}" ABSOLUTE) set(output_path "${MATERIAL_DIR}/${localname}.filamat") - if (NOT FILAMENT_ENABLE_FATURE_LEVEL_0) - # Pick the non-FL0 variant instead. - string(REGEX REPLACE "\\.mat$" "1.mat" fullname "${fullname}") - string(REGEX REPLACE "\\.mat$" "1.mat" mat_src "${mat_src}") - endif() - add_custom_command( OUTPUT ${output_path} COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname} diff --git a/libs/filagui/src/materials/uiBlit1.mat b/libs/filagui/src/materials/uiBlit1.mat deleted file mode 100644 index d0edc109d23..00000000000 --- a/libs/filagui/src/materials/uiBlit1.mat +++ /dev/null @@ -1,28 +0,0 @@ -material { - name : uiBlit, - parameters : [ - { - type : sampler2d, - name : albedo - } - ], - requires : [ - uv0, - color - ], - shadingModel : unlit, - culling : none, - depthCulling: false, - blending : transparent, -} - -fragment { - void material(inout MaterialInputs material) { - prepareMaterial(material); - vec2 uv = getUV0(); - uv.y = 1.0 - uv.y; - vec4 albedo = texture(materialParams_albedo, uv); - material.baseColor = getColor() * albedo; - material.baseColor.rgb *= material.baseColor.a; - } -} diff --git a/libs/filamat/include/filamat/MaterialBuilder.h b/libs/filamat/include/filamat/MaterialBuilder.h index 36c24c4161c..6e7c70a0e21 100644 --- a/libs/filamat/include/filamat/MaterialBuilder.h +++ b/libs/filamat/include/filamat/MaterialBuilder.h @@ -135,6 +135,7 @@ class UTILS_PUBLIC MaterialBuilderBase { Optimization mOptimization = Optimization::PERFORMANCE; bool mPrintShaders = false; bool mGenerateDebugInfo = false; + bool mIncludeEssl1 = true; utils::bitset32 mShaderModels; struct CodeGenParams { ShaderModel shaderModel; @@ -271,6 +272,9 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { MaterialBuilder& noSamplerValidation(bool enabled) noexcept; + //! Enable generation of ESSL 1.0 code in FL0 materials. + MaterialBuilder& includeEssl1(bool enabled) noexcept; + //! Set the name of this material. MaterialBuilder& name(const char* name) noexcept; diff --git a/libs/filamat/src/GLSLPostProcessor.cpp b/libs/filamat/src/GLSLPostProcessor.cpp index db36aae23d9..7c1c4138fd7 100644 --- a/libs/filamat/src/GLSLPostProcessor.cpp +++ b/libs/filamat/src/GLSLPostProcessor.cpp @@ -23,6 +23,7 @@ #include #include +#include "backend/DriverEnums.h" #include "sca/builtinResource.h" #include "sca/GLSLTools.h" @@ -32,6 +33,7 @@ #include "MetalArgumentBuffer.h" #include "SpirvFixup.h" +#include "utils/ostream.h" #include @@ -395,7 +397,9 @@ bool GLSLPostProcessor::process(const std::string& inputShader, Config const& co break; case MaterialBuilder::Optimization::SIZE: case MaterialBuilder::Optimization::PERFORMANCE: - fullOptimization(tShader, config, internalConfig); + if (!fullOptimization(tShader, config, internalConfig)) { + return false; + } break; } @@ -478,7 +482,7 @@ void GLSLPostProcessor::preprocessOptimization(glslang::TShader& tShader, } } -void GLSLPostProcessor::fullOptimization(const TShader& tShader, +bool GLSLPostProcessor::fullOptimization(const TShader& tShader, GLSLPostProcessor::Config const& config, InternalConfig& internalConfig) const { SpirvBlob spirv; @@ -546,19 +550,29 @@ void GLSLPostProcessor::fullOptimization(const TShader& tShader, } } +#ifdef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS *internalConfig.glslOutput = glslCompiler.compile(); +#else + try { + *internalConfig.glslOutput = glslCompiler.compile(); + } catch (spirv_cross::CompilerError e) { + slog.e << "ERROR: " << e.what() << io::endl; + return false; + } +#endif // spirv-cross automatically redeclares gl_ClipDistance if it's used. Some drivers don't // like this, so we simply remove it. // According to EXT_clip_cull_distance, gl_ClipDistance can be // "implicitly sized by indexing it only with integral constant expressions". std::string& str = *internalConfig.glslOutput; - const std::string clipDistanceDefinition = "out float gl_ClipDistance[1];"; + const std::string clipDistanceDefinition = "out float gl_ClipDistance[2];"; size_t const found = str.find(clipDistanceDefinition); if (found != std::string::npos) { str.replace(found, clipDistanceDefinition.length(), ""); } } + return true; } std::shared_ptr GLSLPostProcessor::createOptimizer( diff --git a/libs/filamat/src/GLSLPostProcessor.h b/libs/filamat/src/GLSLPostProcessor.h index 8e3c2e1e4ac..c13dece6369 100644 --- a/libs/filamat/src/GLSLPostProcessor.h +++ b/libs/filamat/src/GLSLPostProcessor.h @@ -93,7 +93,7 @@ class GLSLPostProcessor { ShaderMinifier minifier; }; - void fullOptimization(const glslang::TShader& tShader, + bool fullOptimization(const glslang::TShader& tShader, GLSLPostProcessor::Config const& config, InternalConfig& internalConfig) const; void preprocessOptimization(glslang::TShader& tShader, diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index d175c4e1c63..a95bb077819 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -139,13 +139,13 @@ void MaterialBuilderBase::prepare(bool vulkanSemantics, glTargetLanguage, effectiveFeatureLevel, }); - if (featureLevel == filament::backend::FeatureLevel::FEATURE_LEVEL_0 - && shaderModel == ShaderModel::MOBILE) { - // ESSL1 code may never be compiled to SPIR-V. + if (mIncludeEssl1 + && featureLevel == filament::backend::FeatureLevel::FEATURE_LEVEL_0 + && shaderModel == ShaderModel::MOBILE) { mCodeGenPermutations.push_back({ shaderModel, TargetApi::OPENGL, - TargetLanguage::GLSL, + glTargetLanguage, filament::backend::FeatureLevel::FEATURE_LEVEL_0 }); } @@ -1421,15 +1421,15 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& uniforms.push_back({ "objectUniforms.data[0].morphTargetCount", offsetof(PerRenderableUib, data[0].morphTargetCount), 1, - UniformType::UINT }); + UniformType::INT }); uniforms.push_back({ "objectUniforms.data[0].flagsChannels", offsetof(PerRenderableUib, data[0].flagsChannels), 1, - UniformType::UINT }); + UniformType::INT }); uniforms.push_back({ "objectUniforms.data[0].objectId", offsetof(PerRenderableUib, data[0].objectId), 1, - UniformType::UINT }); + UniformType::INT }); uniforms.push_back({ "objectUniforms.data[0].userData", offsetof(PerRenderableUib, data[0].userData), 1, @@ -1565,4 +1565,9 @@ MaterialBuilder& MaterialBuilder::noSamplerValidation(bool enabled) noexcept { return *this; } +MaterialBuilder& MaterialBuilder::includeEssl1(bool enabled) noexcept { + mIncludeEssl1 = enabled; + return *this; +} + } // namespace filamat diff --git a/libs/filamat/src/shaders/CodeGenerator.cpp b/libs/filamat/src/shaders/CodeGenerator.cpp index e1dbd80ba20..2c20307b7f4 100644 --- a/libs/filamat/src/shaders/CodeGenerator.cpp +++ b/libs/filamat/src/shaders/CodeGenerator.cpp @@ -151,6 +151,36 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade out << "#define FILAMENT_HAS_FEATURE_INSTANCING\n"; } + // During compilation and optimization, __VERSION__ reflects the shader language version of the + // intermediate code, not the version of the final code. spirv-cross automatically adapts + // certain language features (e.g. fragment output) but leaves others untouched (e.g. sampler + // functions, bit shift operations). Client code may have to make decisions based on this + // information, so define a FILAMENT_EFFECTIVE_VERSION constant. + const char *effective_version; + if (mTargetLanguage == TargetLanguage::GLSL) { + effective_version = "__VERSION__"; + } else { + switch (mShaderModel) { + case ShaderModel::MOBILE: + if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) { + effective_version = "300"; + } else { + effective_version = "100"; + } + break; + case ShaderModel::DESKTOP: + if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_2) { + effective_version = "450"; + } else { + effective_version = "410"; + } + break; + default: + assert(false); + } + } + generateDefine(out, "FILAMENT_EFFECTIVE_VERSION", effective_version); + if (stage == ShaderStage::VERTEX) { CodeGenerator::generateDefine(out, "FLIP_UV_ATTRIBUTE", material.flipUV); CodeGenerator::generateDefine(out, "LEGACY_MORPHING", material.useLegacyMorphing); @@ -253,10 +283,13 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade generateSpecializationConstant(out, "CONFIG_POWER_VR_SHADER_WORKAROUNDS", +ReservedSpecializationConstants::CONFIG_POWER_VR_SHADER_WORKAROUNDS, false); - // CONFIG_STEREOSCOPIC_EYES is used to size arrays and on Adreno GPUs + vulkan, this has to + generateSpecializationConstant(out, "CONFIG_STEREO_EYE_COUNT", + +ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT, 2); + + // CONFIG_MAX_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"; + out << "#define CONFIG_MAX_STEREOSCOPIC_EYES " << (int) CONFIG_MAX_STEREOSCOPIC_EYES << "\n"; if (mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) { // On ES2 since we don't have post-processing, we need to emulate EGL_GL_COLORSPACE_KHR, @@ -268,8 +301,9 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade out << '\n'; out << SHADERS_COMMON_DEFINES_GLSL_DATA; - if (mFeatureLevel > FeatureLevel::FEATURE_LEVEL_0 && - material.featureLevel == FeatureLevel::FEATURE_LEVEL_0) { + if (material.featureLevel == FeatureLevel::FEATURE_LEVEL_0 && + (mFeatureLevel > FeatureLevel::FEATURE_LEVEL_0 + || mTargetLanguage == TargetLanguage::SPIRV)) { // Insert compatibility definitions for ESSL 1.0 functions which were removed in ESSL 3.0. // This is the minimum required value according to the OpenGL ES Shading Language Version @@ -457,11 +491,14 @@ io::sstream& CodeGenerator::generateOutput(io::sstream& out, ShaderStage type, const char* materialTypeString = getOutputTypeName(materialOutputType); const char* typeString = getOutputTypeName(outputType); + bool generate_essl3_code = mTargetLanguage == TargetLanguage::SPIRV + || mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1; + out << "\n#define FRAG_OUTPUT" << index << " " << name.c_str(); - if (mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) { - out << "\n#define FRAG_OUTPUT_AT" << index << " gl_FragColor"; - } else { + if (generate_essl3_code) { out << "\n#define FRAG_OUTPUT_AT" << index << " output_" << name.c_str(); + } else { + out << "\n#define FRAG_OUTPUT_AT" << index << " gl_FragColor"; } out << "\n#define FRAG_OUTPUT_MATERIAL_TYPE" << index << " " << materialTypeString; out << "\n#define FRAG_OUTPUT_PRECISION" << index << " " << precisionString; @@ -469,7 +506,7 @@ io::sstream& CodeGenerator::generateOutput(io::sstream& out, ShaderStage type, out << "\n#define FRAG_OUTPUT_SWIZZLE" << index << " " << swizzleString; out << "\n"; - if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) { + if (generate_essl3_code) { out << "\nlayout(location=" << index << ") out " << precisionString << " " << typeString << " output_" << name.c_str() << ";\n"; } diff --git a/libs/filamat/src/shaders/UibGenerator.cpp b/libs/filamat/src/shaders/UibGenerator.cpp index 31932145b47..d1dd2235d0e 100644 --- a/libs/filamat/src/shaders/UibGenerator.cpp +++ b/libs/filamat/src/shaders/UibGenerator.cpp @@ -39,7 +39,7 @@ BufferInterfaceBlock const& UibGenerator::getPerViewUib() noexcept { { "worldFromViewMatrix", 0, Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 }, { "clipFromViewMatrix", 0, Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 }, { "viewFromClipMatrix", 0, Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 }, - { "clipFromWorldMatrix", CONFIG_STEREOSCOPIC_EYES, + { "clipFromWorldMatrix", CONFIG_MAX_STEREOSCOPIC_EYES, Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 }, { "worldFromClipMatrix", 0, Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 }, { "userWorldFromWorldMatrix",0,Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 }, diff --git a/libs/filamentapp/include/filamentapp/Config.h b/libs/filamentapp/include/filamentapp/Config.h index 1343339d808..4f55cf866bb 100644 --- a/libs/filamentapp/include/filamentapp/Config.h +++ b/libs/filamentapp/include/filamentapp/Config.h @@ -34,6 +34,7 @@ struct Config { filament::camutils::Mode cameraMode = filament::camutils::Mode::ORBIT; bool resizeable = true; bool headless = false; + int stereoscopicEyeCount = 2; // Provided to indicate GPU preference for vulkan std::string vulkanGPUHint; diff --git a/libs/filamentapp/include/filamentapp/FilamentApp.h b/libs/filamentapp/include/filamentapp/FilamentApp.h index ce1466362ac..186d146d083 100644 --- a/libs/filamentapp/include/filamentapp/FilamentApp.h +++ b/libs/filamentapp/include/filamentapp/FilamentApp.h @@ -85,6 +85,8 @@ class FilamentApp { PostRenderCallback postRender = PostRenderCallback(), size_t width = 1024, size_t height = 640); + void reconfigureCameras() { mReconfigureCameras = true; } + filament::Material const* getDefaultMaterial() const noexcept { return mDefaultMaterial; } filament::Material const* getTransparentMaterial() const noexcept { return mTransparentMaterial; } IBL* getIBL() const noexcept { return mIBL.get(); } @@ -189,6 +191,7 @@ class FilamentApp { void fixupMouseCoordinatesForHdpi(ssize_t& x, ssize_t& y) const; FilamentApp* const mFilamentApp = nullptr; + Config mConfig; const bool mIsHeadless; SDL_Window* mWindow = nullptr; @@ -251,6 +254,7 @@ class FilamentApp { float mCameraFocalLength = 28.0f; float mCameraNear = 0.1f; float mCameraFar = 100.0f; + bool mReconfigureCameras = false; #if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) filament::backend::VulkanPlatform* mVulkanPlatform = nullptr; diff --git a/libs/filamentapp/src/FilamentApp.cpp b/libs/filamentapp/src/FilamentApp.cpp index 7970c61f3a5..108bb0ac535 100644 --- a/libs/filamentapp/src/FilamentApp.cpp +++ b/libs/filamentapp/src/FilamentApp.cpp @@ -448,6 +448,11 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback, preRender(mEngine, window->mViews[0]->getView(), mScene, renderer); } + if (mReconfigureCameras) { + window->configureCamerasForWindow(); + mReconfigureCameras = false; + } + if (renderer->beginFrame(window->getSwapChain())) { for (filament::View* offscreenView : mOffscreenViews) { renderer->render(offscreenView); @@ -568,7 +573,7 @@ void FilamentApp::initSDL() { FilamentApp::Window::Window(FilamentApp* filamentApp, const Config& config, std::string title, size_t w, size_t h) - : mFilamentApp(filamentApp), mIsHeadless(config.headless) { + : mFilamentApp(filamentApp), mConfig(config), mIsHeadless(config.headless) { const int x = SDL_WINDOWPOS_CENTERED; const int y = SDL_WINDOWPOS_CENTERED; uint32_t windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI; @@ -596,6 +601,9 @@ FilamentApp::Window::Window(FilamentApp* filamentApp, } #endif + Engine::Config engineConfig = {}; + engineConfig.stereoscopicEyeCount = config.stereoscopicEyeCount; + if (backend == Engine::Backend::VULKAN) { #if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) mFilamentApp->mVulkanPlatform = @@ -604,12 +612,14 @@ FilamentApp::Window::Window(FilamentApp* filamentApp, .backend(backend) .platform(mFilamentApp->mVulkanPlatform) .featureLevel(config.featureLevel) + .config(&engineConfig) .build(); #endif } return Engine::Builder() .backend(backend) .featureLevel(config.featureLevel) + .config(&engineConfig) .build(); }; @@ -875,12 +885,23 @@ void FilamentApp::Window::configureCamerasForWindow() { double near = mFilamentApp->mCameraNear; double far = mFilamentApp->mCameraFar; - mMainCamera->setLensProjection(mFilamentApp->mCameraFocalLength, 1.0, near, far); + if (mMainView->getView()->getStereoscopicOptions().enabled) { + mat4 projections[4]; + projections[0] = Camera::projection(mFilamentApp->mCameraFocalLength, 1.0, near, far); + projections[1] = projections[0]; + // simulate foveated rendering + projections[2] = Camera::projection(mFilamentApp->mCameraFocalLength * 2.0, 1.0, near, far); + projections[3] = projections[2]; + mMainCamera->setCustomEyeProjection(projections, 4, projections[0], near, far); + } else { + mMainCamera->setLensProjection(mFilamentApp->mCameraFocalLength, 1.0, near, far); + } mDebugCamera->setProjection(45.0, double(width) / height, 0.0625, 4096, Camera::Fov::VERTICAL); auto aspectRatio = double(mainWidth) / height; if (mMainView->getView()->getStereoscopicOptions().enabled) { - aspectRatio = double(mainWidth) / 2.0 / height; + const int ec = mConfig.stereoscopicEyeCount; + aspectRatio = double(mainWidth) / ec / height; } mMainCamera->setScaling({1.0 / aspectRatio, 1.0}); diff --git a/libs/gltfio/src/ArchiveCache.cpp b/libs/gltfio/src/ArchiveCache.cpp index 47798741fa3..c71065263a0 100644 --- a/libs/gltfio/src/ArchiveCache.cpp +++ b/libs/gltfio/src/ArchiveCache.cpp @@ -118,21 +118,6 @@ Material* ArchiveCache::getMaterial(const ArchiveRequirements& reqs) { mMaterials[i] = Material::Builder() .package(spec.package, spec.packageByteCount) .build(mEngine); - - // Don't attempt to precompile shaders on WebGL. - // Chrome already suffers from slow shader compilation: - // https://github.com/google/filament/issues/6615 - // Precompiling shaders exacerbates the problem. - #if !defined(__EMSCRIPTEN__) - // First compile high priority variants - mMaterials[i]->compile(Material::CompilerPriorityQueue::HIGH, - UserVariantFilterBit::DIRECTIONAL_LIGHTING | - UserVariantFilterBit::DYNAMIC_LIGHTING | - UserVariantFilterBit::SHADOW_RECEIVER); - - // and then, everything else at low priority - mMaterials[i]->compile(Material::CompilerPriorityQueue::LOW); - #endif } return mMaterials[i]; diff --git a/libs/utils/include/utils/JobSystem.h b/libs/utils/include/utils/JobSystem.h index 1c602740de1..041c6d9c55e 100644 --- a/libs/utils/include/utils/JobSystem.h +++ b/libs/utils/include/utils/JobSystem.h @@ -169,8 +169,9 @@ class JobSystem { // the caller must ensure the object will outlive the Job template Job* createJob(Job* parent, T* data) noexcept { - Job* job = create(parent, [](void* user, JobSystem& js, Job* job) { - (*static_cast(user)->*method)(js, job); + Job* job = create(parent, +[](void* storage, JobSystem& js, Job* job) { + T* const that = static_cast(reinterpret_cast(storage)[0]); + (that->*method)(js, job); }); if (job) { job->storage[0] = data; @@ -182,8 +183,8 @@ class JobSystem { template Job* createJob(Job* parent, T data) noexcept { static_assert(sizeof(data) <= sizeof(Job::storage), "user data too large"); - Job* job = create(parent, [](void* user, JobSystem& js, Job* job) { - T* that = static_cast(user); + Job* job = create(parent, [](void* storage, JobSystem& js, Job* job) { + T* const that = static_cast(storage); (that->*method)(js, job); that->~T(); }); @@ -197,10 +198,10 @@ class JobSystem { template Job* createJob(Job* parent, T functor) noexcept { static_assert(sizeof(functor) <= sizeof(Job::storage), "functor too large"); - Job* job = create(parent, [](void* user, JobSystem& js, Job* job){ - T& that = *static_cast(user); - that(js, job); - that.~T(); + Job* job = create(parent, [](void* storage, JobSystem& js, Job* job){ + T* const that = static_cast(storage); + that->operator()(js, job); + that->~T(); }); if (job) { new(job->storage) T(std::move(functor)); @@ -252,7 +253,7 @@ class JobSystem { void signal() noexcept; /* - * Add job to this thread's execution queue and and keep a reference to it. + * Add job to this thread's execution queue and keep a reference to it. * Current thread must be owned by JobSystem's thread pool. See adopt(). * * This job MUST BE waited on with wait(), or released with release(). diff --git a/samples/gltf_viewer.cpp b/samples/gltf_viewer.cpp index 36ecdd8236e..1a09e79f8d9 100644 --- a/samples/gltf_viewer.cpp +++ b/samples/gltf_viewer.cpp @@ -41,6 +41,8 @@ #include +#include + #include #include @@ -58,6 +60,7 @@ #include #include #include +#include #include #include "generated/resources/gltf_demo.h" @@ -180,10 +183,13 @@ static void printUsage(char* name) { " Set the camera mode: orbit (default) or flight\n" " Flight mode uses the following controls:\n" " Click and drag the mouse to pan the camera\n" - " Use the scroll weel to adjust movement speed\n" + " Use the scroll wheel to adjust movement speed\n" " W / S: forward / backward\n" " A / D: left / right\n" " E / Q: up / down\n\n" + " --eyes=, -y \n" + " Sets the number of stereoscopic eyes (default: 2) when stereoscopic rendering is\n" + " enabled.\n\n" " --split-view, -v\n" " Splits the window into 4 views\n\n" " --vulkan-gpu-hint=, -g\n" @@ -215,6 +221,7 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) { { "ubershader", no_argument, nullptr, 'u' }, { "actual-size", no_argument, nullptr, 's' }, { "camera", required_argument, nullptr, 'c' }, + { "eyes", required_argument, nullptr, 'y' }, { "recompute-aabb", no_argument, nullptr, 'r' }, { "settings", required_argument, nullptr, 't' }, { "split-view", no_argument, nullptr, 'v' }, @@ -261,6 +268,19 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) { std::cerr << "Unrecognized camera mode. Must be 'flight'|'orbit'.\n"; } break; + case 'y': { + int eyeCount = 0; + try { + eyeCount = std::stoi(arg); + } catch (std::invalid_argument &e) { } + if (eyeCount >= 1 && eyeCount <= CONFIG_MAX_STEREOSCOPIC_EYES) { + app->config.stereoscopicEyeCount = eyeCount; + } else { + std::cerr << "Eye count must be between 1 and CONFIG_MAX_STEREOSCOPIC_EYES (" + << (int) CONFIG_MAX_STEREOSCOPIC_EYES << ") (inclusive).\n"; + } + break; + } case 'e': app->config.headless = true; break; @@ -578,6 +598,41 @@ int main(int argc, char** argv) { exit(1); } + // pre-compile all material variants + std::set materials; + RenderableManager const& rcm = app.engine->getRenderableManager(); + Slice const renderables{ + app.asset->getRenderableEntities(), app.asset->getRenderableEntityCount() }; + for (Entity const e: renderables) { + auto ri = rcm.getInstance(e); + size_t const c = rcm.getPrimitiveCount(ri); + for (size_t i = 0; i < c; i++) { + MaterialInstance* const mi = rcm.getMaterialInstanceAt(ri, i); + Material* ma = const_cast(mi->getMaterial()); + materials.insert(ma); + } + } + for (Material* ma : materials) { + // Don't attempt to precompile shaders on WebGL. + // Chrome already suffers from slow shader compilation: + // https://github.com/google/filament/issues/6615 + // Precompiling shaders exacerbates the problem. +#if !defined(__EMSCRIPTEN__) + // First compile high priority variants + ma->compile(Material::CompilerPriorityQueue::HIGH, + UserVariantFilterBit::DIRECTIONAL_LIGHTING | + UserVariantFilterBit::DYNAMIC_LIGHTING | + UserVariantFilterBit::SHADOW_RECEIVER); + + // and then, everything else at low priority, except STE, which is very uncommon. + ma->compile(Material::CompilerPriorityQueue::LOW, + UserVariantFilterBit::FOG | + UserVariantFilterBit::SKINNING | + UserVariantFilterBit::SSR | + UserVariantFilterBit::VSM); +#endif + } + app.instance = app.asset->getInstance(); buffer.clear(); buffer.shrink_to_fit(); @@ -987,25 +1042,27 @@ int main(int argc, char** argv) { if (view->getStereoscopicOptions().enabled) { Camera& c = view->getCamera(); auto od = app.viewer->getOcularDistance(); - // Eye 0 is always rendered to the left side of the screen; Eye 1, the right side. + // Eyes are rendered from left-to-right, i.e., eye 0 is rendered to the left side of the + // window. // For testing, we want to render a side-by-side layout so users can view with // "cross-eyed" stereo. // For cross-eyed stereo, Eye 0 is really the RIGHT eye, while Eye 1 is the LEFT eye. const mat4 rightEye = mat4::translation(double3{ od, 0.0, 0.0}); // right eye const mat4 leftEye = mat4::translation(double3{-od, 0.0, 0.0}); // left eye - c.setEyeModelMatrix(0, rightEye); - c.setEyeModelMatrix(1, leftEye); - mat4 projections[2]; - // Use an aspect ratio of 1.0. The viewport will be taken into account in - // FilamentApp.cpp. - projections[0] = mat4::perspective(70.0, 1.0, .1, 10.0); - projections[1] = mat4::perspective(70.0, 1.0, .1, 10.0); - c.setCustomEyeProjection(projections, 2, projections[0], .1, 10.0); - // FIXME: the aspect ratio will be incorrect until configureCamerasForWindow is - // triggered, which will happen the next time the window is resized. + const mat4 modelMatrices[4] = { rightEye, leftEye, rightEye, leftEye }; + for (int i = 0; i < std::min(app.config.stereoscopicEyeCount, 4); i++) { + c.setEyeModelMatrix(i, modelMatrices[i]); + } } else { - view->getCamera().setEyeModelMatrix(0, {}); - view->getCamera().setEyeModelMatrix(1, {}); + for (int i = 0; i < app.config.stereoscopicEyeCount; i++) { + view->getCamera().setEyeModelMatrix(i, {}); + } + } + static bool stereoscopicEnabled = false; + if (stereoscopicEnabled != view->getStereoscopicOptions().enabled) { + // Stereo was turned on/off. + FilamentApp::get().reconfigureCameras(); + stereoscopicEnabled = view->getStereoscopicOptions().enabled; } app.scene.groundMaterial->setDefaultParameter( diff --git a/shaders/src/common_getters.glsl b/shaders/src/common_getters.glsl index 54ee7be33dd..718ea05c5b4 100644 --- a/shaders/src/common_getters.glsl +++ b/shaders/src/common_getters.glsl @@ -25,7 +25,7 @@ highp mat4 getViewFromClipMatrix() { /** @public-api */ highp mat4 getClipFromWorldMatrix() { #if defined(VARIANT_HAS_INSTANCED_STEREO) - int eye = instance_index % CONFIG_STEREOSCOPIC_EYES; + int eye = instance_index % CONFIG_STEREO_EYE_COUNT; return frameUniforms.clipFromWorldMatrix[eye]; #else return frameUniforms.clipFromWorldMatrix[0]; diff --git a/shaders/src/depth_main.fs b/shaders/src/depth_main.fs index 77608f3e15c..e50d7620f17 100644 --- a/shaders/src/depth_main.fs +++ b/shaders/src/depth_main.fs @@ -57,7 +57,7 @@ void main() { fragColor.xy = computeDepthMomentsVSM(depth); fragColor.zw = computeDepthMomentsVSM(-1.0 / depth); // requires at least RGBA16F #elif defined(VARIANT_HAS_PICKING) -#if MATERIAL_FEATURE_LEVEL == 0 +#if FILAMENT_EFFECTIVE_VERSION == 100 outPicking.a = mod(float(object_uniforms_objectId / 65536), 256.0) / 255.0; outPicking.b = mod(float(object_uniforms_objectId / 256), 256.0) / 255.0; outPicking.g = mod(float(object_uniforms_objectId) , 256.0) / 255.0; diff --git a/shaders/src/main.vs b/shaders/src/main.vs index a52bc527d1d..6856d21a89f 100644 --- a/shaders/src/main.vs +++ b/shaders/src/main.vs @@ -27,9 +27,8 @@ void main() { #if !defined(FILAMENT_HAS_FEATURE_INSTANCING) #error Instanced stereo not supported at this feature level #endif - // The lowest bit of the instance index represents the eye. - // This logic must be updated if CONFIG_STEREOSCOPIC_EYES changes - logical_instance_index = instance_index >> 1; + // Calculate the logical instance index, which is the instance index within a single eye. + logical_instance_index = instance_index / CONFIG_STEREO_EYE_COUNT; #endif initObjectUniforms(); @@ -217,22 +216,26 @@ void main() { vertex_position = position; #if defined(VARIANT_HAS_INSTANCED_STEREO) - // This logic must be updated if CONFIG_STEREOSCOPIC_EYES changes // We're transforming a vertex whose x coordinate is within the range (-w to w). - // To move it to the correct half of the viewport, we need to modify the x coordinate: - // Eye 0 (left half): (-w to 0) - // Eye 1 (right half): ( 0 to w) + // To move it to the correct portion of the viewport, we need to modify the x coordinate. // It's important to do this after computing vertex_position. - int eyeIndex = instance_index % 2; - float eyeShift = float(eyeIndex) * 2.0f - 1.0f; // eye 0: -1.0, eye 1: 1.0 - position.x = position.x * 0.5f + (position.w * 0.5 * eyeShift); - - // A fragment is clipped when gl_ClipDistance is negative (outside the clip plane). So, - // Eye 0 should have a positive value when x is < 0 - // -position.x - // Eye 1 should have a positive value when x is > 0 - // position.x - FILAMENT_CLIPDISTANCE[0] = position.x * eyeShift; + int eyeIndex = instance_index % CONFIG_STEREO_EYE_COUNT; + + float ndcViewportWidth = 2.0 / float(CONFIG_STEREO_EYE_COUNT); // the width of ndc space is 2 + float eyeZeroMidpoint = -1.0f + ndcViewportWidth / 2.0; + + float transform = eyeZeroMidpoint + ndcViewportWidth * float(eyeIndex); + position.x *= 1.0 / float(CONFIG_STEREO_EYE_COUNT); + position.x += transform * position.w; + + // A fragment is clipped when gl_ClipDistance is negative (outside the clip plane). + + float leftClip = position.x + + (1.0 - ndcViewportWidth * float(eyeIndex)) * position.w; + float rightClip = position.x + + (1.0 - ndcViewportWidth * float(eyeIndex + 1)) * position.w; + FILAMENT_CLIPDISTANCE[0] = leftClip; + FILAMENT_CLIPDISTANCE[1] = -rightClip; #endif #if defined(TARGET_VULKAN_ENVIRONMENT) diff --git a/shaders/src/varyings.glsl b/shaders/src/varyings.glsl index 40eb3723504..fe5c24d2477 100644 --- a/shaders/src/varyings.glsl +++ b/shaders/src/varyings.glsl @@ -40,7 +40,7 @@ LAYOUT_LOCATION(11) VARYING highp vec4 vertex_lightSpacePosition; // However, this extension is not supported by glslang, so we instead write to // filament_gl_ClipDistance, which will get decorated at the SPIR-V stage to refer to the built-in. // The location here does not matter, so long as it doesn't conflict with others. -LAYOUT_LOCATION(100) out float filament_gl_ClipDistance[1]; +LAYOUT_LOCATION(100) out float filament_gl_ClipDistance[2]; #define FILAMENT_CLIPDISTANCE filament_gl_ClipDistance #else // If we're on Desktop GL (or not running shaders through glslang), we're free to use gl_ClipDistance diff --git a/third_party/spirv-cross/spirv_glsl.cpp b/third_party/spirv-cross/spirv_glsl.cpp index 0d63d35f8f2..e0b39f95bb7 100644 --- a/third_party/spirv-cross/spirv_glsl.cpp +++ b/third_party/spirv-cross/spirv_glsl.cpp @@ -14977,7 +14977,12 @@ string CompilerGLSL::flags_to_qualifiers_glsl(const SPIRType &type, const Bitset { auto &execution = get_entry_point(); - if (flags.get(DecorationRelaxedPrecision)) + if (type.basetype == SPIRType::UInt && is_legacy_es()) + { + // HACK: This is a bool. See comment in type_to_glsl(). + qual += "lowp "; + } + else if (flags.get(DecorationRelaxedPrecision)) { bool implied_fmediump = type.basetype == SPIRType::Float && options.fragment.default_float_precision == Options::Mediump && @@ -15503,7 +15508,11 @@ string CompilerGLSL::type_to_glsl(const SPIRType &type, uint32_t id) if (type.basetype == SPIRType::UInt && is_legacy()) { if (options.es) - SPIRV_CROSS_THROW("Unsigned integers are not supported on legacy ESSL."); + // HACK: spirv-cross changes bools into uints and generates code which compares them to + // zero. Input code will have already been validated as not to have contained any uints, + // so any remaining uints must in fact be bools. However, simply returning "bool" here + // will result in invalid code. Instead, return an int. + return backend.basic_int_type; else require_extension_internal("GL_EXT_gpu_shader4"); } diff --git a/tools/matc/src/matc/CommandlineConfig.cpp b/tools/matc/src/matc/CommandlineConfig.cpp index 5323fb87419..30d6114e99d 100644 --- a/tools/matc/src/matc/CommandlineConfig.cpp +++ b/tools/matc/src/matc/CommandlineConfig.cpp @@ -62,6 +62,9 @@ static void usage(char* name) { " MATC --api opengl --api metal ...\n\n" " --feature-level, -l\n" " Specify the maximum feature level allowed (default is 3).\n\n" + " --no-essl1, -1\n" + " Don't generate ESSL 1.0 code even for Feature Level 0 mobile shaders.\n" + " Shaders are still validated against ESSL 1.0.\n\n" " --define, -D\n" " Add a preprocessor define macro via =. defaults to 1 if omitted.\n" " Can be repeated to specify multiple definitions:\n" @@ -171,7 +174,7 @@ static void parseDefine(std::string defineString, Config::StringReplacementMap& } bool CommandlineConfig::parse() { - static constexpr const char* OPTSTR = "hLxo:f:dm:a:l:p:D:T:OSEr:vV:gtwF"; + static constexpr const char* OPTSTR = "hLxo:f:dm:a:l:p:D:T:OSEr:vV:gtwF1"; static const struct option OPTIONS[] = { { "help", no_argument, nullptr, 'h' }, { "license", no_argument, nullptr, 'L' }, @@ -187,6 +190,7 @@ bool CommandlineConfig::parse() { { "preprocessor-only", no_argument, nullptr, 'E' }, { "api", required_argument, nullptr, 'a' }, { "feature-level", required_argument, nullptr, 'l' }, + { "no-essl1", no_argument, nullptr, '1' }, { "define", required_argument, nullptr, 'D' }, { "template", required_argument, nullptr, 'T' }, { "reflect", required_argument, nullptr, 'r' }, @@ -270,6 +274,9 @@ bool CommandlineConfig::parse() { } break; } + case '1': + mIncludeEssl1 = false; + break; case 'D': parseDefine(arg, mDefines); break; diff --git a/tools/matc/src/matc/Config.h b/tools/matc/src/matc/Config.h index c5403d693d3..79c6786541a 100644 --- a/tools/matc/src/matc/Config.h +++ b/tools/matc/src/matc/Config.h @@ -119,6 +119,10 @@ class Config { return mNoSamplerValidation; } + bool includeEssl1() const noexcept { + return mIncludeEssl1; + } + filament::UserVariantFilterMask getVariantFilter() const noexcept { return mVariantFilter; } @@ -150,6 +154,7 @@ class Config { StringReplacementMap mDefines; StringReplacementMap mTemplateMap; filament::UserVariantFilterMask mVariantFilter = 0; + bool mIncludeEssl1 = true; }; } diff --git a/tools/matc/src/matc/MaterialCompiler.cpp b/tools/matc/src/matc/MaterialCompiler.cpp index d9920d4c059..b21f75f84c5 100644 --- a/tools/matc/src/matc/MaterialCompiler.cpp +++ b/tools/matc/src/matc/MaterialCompiler.cpp @@ -399,6 +399,7 @@ bool MaterialCompiler::run(const Config& config) { builder .noSamplerValidation(config.noSamplerValidation()) + .includeEssl1(config.includeEssl1()) .includeCallback(includer) .fileName(materialFilePath.getName().c_str()) .platform(config.getPlatform()) diff --git a/web/filament-js/package.json b/web/filament-js/package.json index d91ac748924..ef4cd7e79d9 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.46.0", + "version": "1.47.0", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js",