diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index fe9ef6f63c..5fe74b5913 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -5,6 +5,8 @@ A new header is inserted each time a *tag* is created. ## main branch +- engine: new feature level APIs, see `Engine::getSupportedFeatureLevel()` + ## v1.25.6 - Add CONFIG_MINSPEC_UBO_SIZE as a nicer way to allow exceeding the ES3.0 minspec. diff --git a/android/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index 882cdeb939..3d720a88a3 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -328,3 +328,24 @@ Java_com_google_android_filament_Engine_nIsAutomaticInstancingEnabled(JNIEnv*, j Engine* engine = (Engine*) nativeEngine; return (jboolean)engine->isAutomaticInstancingEnabled(); } + +extern "C" JNIEXPORT jint JNICALL +Java_com_google_android_filament_Engine_nGetSupportedFeatureLevel(JNIEnv *, jclass, + jlong nativeEngine) { + Engine* engine = (Engine*) nativeEngine; + return (jint)engine->getSupportedFeatureLevel(); +} + +extern "C" JNIEXPORT jint JNICALL +Java_com_google_android_filament_Engine_nSetActiveFeatureLevel(JNIEnv *, jclass, + jlong nativeEngine, jint ordinal) { + Engine* engine = (Engine*) nativeEngine; + return (jint)engine->setActiveFeatureLevel((Engine::FeatureLevel)ordinal); +} + +extern "C" JNIEXPORT jint JNICALL +Java_com_google_android_filament_Engine_nGetActiveFeatureLevel(JNIEnv *, jclass, + jlong nativeEngine) { + Engine* engine = (Engine*) nativeEngine; + return (jint)engine->getActiveFeatureLevel(); +} \ No newline at end of file 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 58b0482f09..c047c96224 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 @@ -47,7 +47,7 @@ import com.google.android.filament.proguard.UsedByReflection; *
* import com.google.android.filament.*
*
- * Engin engine = Engine.create();
+ * Engine engine = Engine.create();
* SwapChain swapChain = engine.createSwapChain(nativeWindow);
* Renderer renderer = engine.createRenderer();
* Scene scene = engine.createScene();
@@ -107,6 +107,7 @@ import com.google.android.filament.proguard.UsedByReflection;
*/
public class Engine {
private static final Backend[] sBackendValues = Backend.values();
+ private static final FeatureLevel[] sFeatureLevelValues = FeatureLevel.values();
private long mNativeObject;
@@ -141,6 +142,18 @@ public class Engine {
NOOP,
}
+ /**
+ * Defines the backend's feature levels.
+ */
+ public enum FeatureLevel {
+ /** Reserved, don't use */
+ FEATURE_LEVEL_0,
+ /** OpenGL ES 3.0 features (default) */
+ FEATURE_LEVEL_1,
+ /** OpenGL ES 3.1 features + 31 textures units + cubemap arrays */
+ FEATURE_LEVEL_2
+ };
+
private Engine(long nativeEngine) {
mNativeObject = nativeEngine;
mTransformManager = new TransformManager(nGetTransformManager(nativeEngine));
@@ -269,6 +282,50 @@ public class Engine {
getTransformManager().setAccurateTranslationsEnabled(true);
}
+ /**
+ * Query the feature level supported by the selected backend.
+ *
+ * A specific feature level needs to be set before the corresponding features can be used.
+ *
+ * @return FeatureLevel supported the selected backend.
+ * @see #setActiveFeatureLevel
+ */
+ @NonNull
+ public FeatureLevel getSupportedFeatureLevel() {
+ return sFeatureLevelValues[(int) nGetSupportedFeatureLevel(getNativeObject())];
+ }
+
+ /**
+ * Activate all features of a given feature level. By default FeatureLevel::FEATURE_LEVEL_1 is
+ * active. The selected feature level must not be higher than the value returned by
+ * getActiveFeatureLevel() and it's not possible lower the active feature level.
+ *
+ * @param featureLevel the feature level to activate. If featureLevel is lower than
+ * getActiveFeatureLevel(), the current (higher) feature level is kept.
+ * If featureLevel is higher than getSupportedFeatureLevel(), an exception
+ * is thrown, or the program is terminated if exceptions are disabled.
+ *
+ * @return the active feature level.
+ *
+ * @see #getSupportedFeatureLevel
+ * @see #getActiveFeatureLevel
+ */
+ @NonNull
+ public FeatureLevel setActiveFeatureLevel(@NonNull FeatureLevel featureLevel) {
+ return sFeatureLevelValues[(int) nSetActiveFeatureLevel(getNativeObject(), featureLevel.ordinal())];
+ }
+
+ /**
+ * Returns the currently active feature level.
+ * @return currently active feature level
+ * @see #getSupportedFeatureLevel
+ * @see #setActiveFeatureLevel
+ */
+ @NonNull
+ public FeatureLevel getActiveFeatureLevel() {
+ return sFeatureLevelValues[(int) nGetActiveFeatureLevel(getNativeObject())];
+ }
+
/**
* Enables or disables automatic instancing of render primitives. Instancing of render primitive
* can greatly reduce CPU overhead but requires the instanced primitives to be identical
@@ -289,7 +346,7 @@ public class Engine {
/**
* @return true if automatic instancing is enabled, false otherwise.
- * @see setAutomaticInstancingEnabled
+ * @see #setAutomaticInstancingEnabled
*/
public boolean isAutomaticInstancingEnabled() {
return nIsAutomaticInstancingEnabled(getNativeObject());
@@ -760,4 +817,7 @@ public class Engine {
private static native long nGetEntityManager(long nativeEngine);
private static native void nSetAutomaticInstancingEnabled(long nativeEngine, boolean enable);
private static native boolean nIsAutomaticInstancingEnabled(long nativeEngine);
+ private static native int nGetSupportedFeatureLevel(long nativeEngine);
+ private static native int nSetActiveFeatureLevel(long nativeEngine, int ordinal);
+ private static native int nGetActiveFeatureLevel(long nativeEngine);
}
diff --git a/docs/Materials.md.html b/docs/Materials.md.html
index c61f2811f0..41bfd51919 100644
--- a/docs/Materials.md.html
+++ b/docs/Materials.md.html
@@ -932,6 +932,36 @@ material {
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+### General: featureLevel
+
+Type
+: `number`
+
+Value
+: An integer value, either 1 or 2. Defaults to 1.
+
+ Feature Level | Guaranteed features
+:----------------------|:---------------------------------
+1 | 9 textures per material
+2 | 12 textures per material, cubemap arrays, ESSL 3.10
+[Table [featureLevels]: Feature levels]
+
+Description
+: Sets the feature level of the material. Each feature level defines a set of features the
+ material can use. If the material uses a feature not supported by the selected level, `matc`
+ will generate an error during compilation. A given feature level is guaranteed to support
+ all features of lower feature levels.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
+material {
+ featureLevel : 2
+}
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Bugs
+: `matc` doesn't verify that a material is not using features above its selected feature level.
+
+
### General: shadingModel
Type
diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h
index ba6990ddce..1c267bbef2 100644
--- a/filament/backend/include/backend/DriverEnums.h
+++ b/filament/backend/include/backend/DriverEnums.h
@@ -59,6 +59,14 @@ static_assert(MAX_VERTEX_BUFFER_COUNT <= MAX_VERTEX_ATTRIBUTE_COUNT,
static constexpr size_t CONFIG_BINDING_COUNT = 12; // This is guaranteed by OpenGL ES.
+/**
+ * Defines the backend's feature levels.
+ */
+enum class FeatureLevel : uint8_t {
+ FEATURE_LEVEL_1 = 1, //!< OpenGL ES 3.0 features (default)
+ FEATURE_LEVEL_2 //!< OpenGL ES 3.1 features + 31 textures units + cubemap arrays
+};
+
/**
* Selects which driver a particular Engine should use.
*/
diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc
index d989bcb11a..cbfb84e82f 100644
--- a/filament/backend/include/private/backend/DriverAPI.inc
+++ b/filament/backend/include/private/backend/DriverAPI.inc
@@ -304,6 +304,7 @@ DECL_DRIVER_API_SYNCHRONOUS_N(void, cancelExternalImage, void*, image)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, getTimerQueryValue, backend::TimerQueryHandle, query, uint64_t*, elapsedTime)
DECL_DRIVER_API_SYNCHRONOUS_N(backend::SyncStatus, getSyncStatus, backend::SyncHandle, sh)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isWorkaroundNeeded, backend::Workaround, workaround)
+DECL_DRIVER_API_SYNCHRONOUS_0(backend::FeatureLevel, getFeatureLevel)
/*
* Updating driver objects
diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm
index 61a7c9390b..71c2be2997 100644
--- a/filament/backend/src/metal/MetalDriver.mm
+++ b/filament/backend/src/metal/MetalDriver.mm
@@ -688,6 +688,13 @@ bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
return false;
}
+FeatureLevel MetalDriver::getFeatureLevel() {
+ // TODO: before we can return FEATURE_LEVEL_2 we need to decouple the samplers from
+ // the textures. Metal only supports 16 samplers, but at least 31 textures on all
+ // hardware.
+ return FeatureLevel::FEATURE_LEVEL_1;
+}
+
math::float2 MetalDriver::getClipSpaceParams() {
// virtual and physical z-coordinate of clip-space is in [-w, 0]
// Note: this is actually never used (see: main.vs), but it's a backend API so we implement it
diff --git a/filament/backend/src/noop/NoopDriver.cpp b/filament/backend/src/noop/NoopDriver.cpp
index 4b34b4d2b0..992695d9b0 100644
--- a/filament/backend/src/noop/NoopDriver.cpp
+++ b/filament/backend/src/noop/NoopDriver.cpp
@@ -177,6 +177,10 @@ bool NoopDriver::isWorkaroundNeeded(Workaround workaround) {
return false;
}
+FeatureLevel NoopDriver::getFeatureLevel() {
+ return FeatureLevel::FEATURE_LEVEL_1;
+}
+
math::float2 NoopDriver::getClipSpaceParams() {
return math::float2{ 1.0f, 0.0f };
}
diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp
index 9459d4c092..98198128ce 100644
--- a/filament/backend/src/opengl/OpenGLContext.cpp
+++ b/filament/backend/src/opengl/OpenGLContext.cpp
@@ -40,7 +40,7 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept {
OpenGLContext::OpenGLContext() noexcept {
state.vao.p = &mDefaultVAO;
- // There query work with all GL/GLES versions!
+ // These queries work with all GL/GLES versions!
state.vendor = (char const*)glGetString(GL_VENDOR);
state.renderer = (char const*)glGetString(GL_RENDERER);
state.version = (char const*)glGetString(GL_VERSION);
@@ -55,26 +55,56 @@ OpenGLContext::OpenGLContext() noexcept {
queryOpenGLVersion(&state.major, &state.minor);
+ glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &gets.max_renderbuffer_size);
+ glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &gets.max_uniform_block_size);
+ glGetIntegerv(GL_MAX_SAMPLES, &gets.max_samples);
+ glGetIntegerv(GL_MAX_DRAW_BUFFERS, &gets.max_draw_buffers);
+ glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &gets.max_texture_image_units);
+ glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &gets.uniform_buffer_offset_alignment);
+
if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GLES) {
- // This works on all versions of GLES
- if (state.major == 3 && state.minor >= 0) {
- mShaderModel = ShaderModel::MOBILE;
- }
- if (state.major == 3 && state.minor >= 1) {
- features.multisample_texture = true;
- }
initExtensionsGLES();
+ if (state.major == 3) {
+ assert_invariant(gets.max_texture_image_units >= 16);
+ if (state.minor >= 0) {
+ mShaderModel = ShaderModel::MOBILE;
+ }
+ if (state.minor >= 1) {
+ features.multisample_texture = true;
+ // figure out our feature level
+ if (gets.max_texture_image_units >= 31 &&
+ ext.EXT_texture_cube_map_array) {
+ mFeatureLevel = FeatureLevel::FEATURE_LEVEL_2;
+ }
+ }
+ }
} else if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL) {
// OpenGL version
- if (state.major == 4 && state.minor >= 1) {
- mShaderModel = ShaderModel::DESKTOP;
+ initExtensionsGL();
+ if (state.major == 4) {
+ if (state.minor >= 1) {
+ mShaderModel = ShaderModel::DESKTOP;
+ }
+ if (state.minor >= 3) {
+ // figure out our feature level
+ if (gets.max_texture_image_units >= 31) {
+ // cubemap arrays are available as of OpenGL 4.0
+ mFeatureLevel = FeatureLevel::FEATURE_LEVEL_2;
+ }
+ }
+ features.multisample_texture = true;
}
- 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;
- initExtensionsGL();
+ assert_invariant(gets.max_texture_image_units >= 16);
}
+#ifdef GL_EXT_texture_filter_anisotropic
+ if (ext.EXT_texture_filter_anisotropic) {
+ glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gets.max_anisotropy);
+ }
+#endif
+
/*
* Figure out which driver bugs we need to workaround
*/
@@ -192,18 +222,6 @@ OpenGLContext::OpenGLContext() noexcept {
}
flush(slog.v);
- // now we can query getter and features
- glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &gets.max_renderbuffer_size);
- glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &gets.max_uniform_block_size);
- 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);
-#ifdef GL_EXT_texture_filter_anisotropic
- if (ext.EXT_texture_filter_anisotropic) {
- glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gets.max_anisotropy);
- }
-#endif
-
assert_invariant(gets.max_draw_buffers >= 4); // minspec
#ifndef NDEBUG
@@ -213,6 +231,7 @@ OpenGLContext::OpenGLContext() noexcept {
<< "GL_MAX_SAMPLES = " << gets.max_samples << '\n'
<< "GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT = " << gets.max_anisotropy << '\n'
<< "GL_MAX_UNIFORM_BLOCK_SIZE = " << gets.max_uniform_block_size << '\n'
+ << "GL_MAX_TEXTURE_IMAGE_UNITS = " << gets.max_texture_image_units << '\n'
<< "GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT = " << gets.uniform_buffer_offset_alignment << '\n'
;
flush(slog.v);
@@ -328,18 +347,18 @@ void OpenGLContext::initExtensionsGLES() noexcept {
#if !defined(__EMSCRIPTEN__)
ext.EXT_texture_compression_etc2 = true;
#endif
- ext.EXT_texture_filter_anisotropic = exts.has("GL_EXT_texture_filter_anisotropic"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 = 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_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_EGL_image_external_essl3 = exts.has("GL_OES_EGL_image_external_essl3"sv);
ext.QCOM_tiled_rendering = exts.has("GL_QCOM_tiled_rendering"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.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.WEBGL_compressed_texture_etc = exts.has("WEBGL_compressed_texture_etc"sv);
// ES 3.2 implies EXT_color_buffer_float
if (state.major >= 3 && state.minor >= 2) {
ext.EXT_color_buffer_float = true;
@@ -371,6 +390,8 @@ void OpenGLContext::initExtensionsGL() noexcept {
ext.EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
ext.EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
ext.EXT_texture_compression_etc2 = exts.has("GL_ARB_ES3_compatibility"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_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);
@@ -378,8 +399,6 @@ void OpenGLContext::initExtensionsGL() noexcept {
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_EGL_image_external_essl3 = exts.has("GL_OES_EGL_image_external_essl3"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);
}
void OpenGLContext::bindBuffer(GLenum target, GLuint buffer) noexcept {
diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h
index e78497f05b..54aaa68259 100644
--- a/filament/backend/src/opengl/OpenGLContext.h
+++ b/filament/backend/src/opengl/OpenGLContext.h
@@ -125,6 +125,7 @@ public:
GLint max_renderbuffer_size;
GLint max_samples;
GLint max_uniform_block_size;
+ GLint max_texture_image_units;
GLint uniform_buffer_offset_alignment;
} gets = {};
@@ -150,6 +151,7 @@ public:
bool EXT_texture_compression_etc2 = false;
bool EXT_texture_compression_s3tc = false;
bool EXT_texture_compression_s3tc_srgb = false;
+ bool EXT_texture_cube_map_array = false;
bool EXT_texture_filter_anisotropic = false;
bool EXT_texture_sRGB = false;
bool GOOGLE_cpp_style_line_directive = false;
@@ -210,7 +212,8 @@ public:
// GL and Vulkan and probably Metal.
bool allow_read_only_ancillary_feedback_loop = false;
- // Some Adreno drivers would crash gl draw when there's uninitialized uniform array exists, even when the code using that uniform array is not called. it's needed to do an initialization to avoid it.
+ // Some Adreno drivers crash in glDrawXXX() when there's an uninitialized uniform block,
+ // even when the shader doesn't access it.
bool enable_initialize_non_used_uniform_array = false;
} bugs;
@@ -224,6 +227,8 @@ public:
}
void resetProgram() noexcept { state.program.use = 0; }
+ FeatureLevel getFeatureLevel() const noexcept { return mFeatureLevel; }
+
// Try to keep the State structure sorted by data-access patterns
struct State {
GLint major = 0;
@@ -343,6 +348,7 @@ public:
private:
ShaderModel mShaderModel = ShaderModel::MOBILE;
+ FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1;
const std::array, sizeof(bugs)> mBugDatabase{{
{ bugs.disable_glFlush,
diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp
index 81299b25cc..c6c76be3c1 100644
--- a/filament/backend/src/opengl/OpenGLDriver.cpp
+++ b/filament/backend/src/opengl/OpenGLDriver.cpp
@@ -1633,6 +1633,10 @@ bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
return false;
}
+FeatureLevel OpenGLDriver::getFeatureLevel() {
+ return mContext.getFeatureLevel();
+}
+
math::float2 OpenGLDriver::getClipSpaceParams() {
return mContext.ext.EXT_clip_control ?
// z-coordinate of virtual and physical clip-space is in [-w, 0]
diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp
index 81d6d53ba1..66626a683b 100644
--- a/filament/backend/src/vulkan/VulkanDriver.cpp
+++ b/filament/backend/src/vulkan/VulkanDriver.cpp
@@ -884,9 +884,21 @@ bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
return false;
}
+FeatureLevel VulkanDriver::getFeatureLevel() {
+ const auto supportedSamplerCount = std::min(
+ mContext.physicalDeviceProperties.limits.maxDescriptorSetSamplers,
+ mContext.physicalDeviceProperties.limits.maxDescriptorSetSampledImages);
+
+ const bool imageCubeArray = (bool)mContext.physicalDeviceFeatures.imageCubeArray;
+
+ return (supportedSamplerCount >= 31 && imageCubeArray) ?
+ FeatureLevel::FEATURE_LEVEL_2 :
+ FeatureLevel::FEATURE_LEVEL_1;
+}
+
math::float2 VulkanDriver::getClipSpaceParams() {
// virtual and physical z-coordinate of clip-space is in [-w, 0]
- // Note: this is actually never used (see: main.vs), but it's a backend API so we implement it
+ // Note: this is actually never used (see: main.vs), but it's a backend API, so we implement it
// properly.
return math::float2{ 1.0f, 0.0f };
}
diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.cpp b/filament/backend/src/vulkan/VulkanPipelineCache.cpp
index 3c1e876888..4b296f437f 100644
--- a/filament/backend/src/vulkan/VulkanPipelineCache.cpp
+++ b/filament/backend/src/vulkan/VulkanPipelineCache.cpp
@@ -695,7 +695,7 @@ void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& cmdbuffer)
++mCurrentTime;
// The Vulkan spec says: "When a command buffer begins recording, all state in that command
- // buffer is undefined." Therefore we need to clear all bindings at this time.
+ // buffer is undefined." Therefore, we need to clear all bindings at this time.
mBoundPipeline = {};
mBoundLayout = {};
mBoundDescriptor = {};
diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h
index 81085d4efb..71d17803a3 100644
--- a/filament/include/filament/Engine.h
+++ b/filament/include/filament/Engine.h
@@ -403,6 +403,45 @@ public:
*/
static void destroy(Engine* engine);
+ using FeatureLevel = backend::FeatureLevel;
+
+
+ /**
+ * Query the feature level supported by the selected backend.
+ *
+ * A specific feature level needs to be set before the corresponding features can be used.
+ *
+ * @return FeatureLevel supported the selected backend.
+ * @see setActiveFeatureLevel
+ */
+ FeatureLevel getSupportedFeatureLevel() const noexcept;
+
+ /**
+ * Activate all features of a given feature level. By default FeatureLevel::FEATURE_LEVEL_1 is
+ * active. The selected feature level must not be higher than the value returned by
+ * getActiveFeatureLevel() and it's not possible lower the active feature level.
+ *
+ * @param featureLevel the feature level to activate. If featureLevel is lower than
+ * getActiveFeatureLevel(), the current (higher) feature level is kept.
+ * If featureLevel is higher than getSupportedFeatureLevel(), an exception
+ * is thrown, or the program is terminated if exceptions are disabled.
+ *
+ * @return the active feature level.
+ *
+ * @see getSupportedFeatureLevel
+ * @see getActiveFeatureLevel
+ */
+ FeatureLevel setActiveFeatureLevel(FeatureLevel featureLevel);
+
+ /**
+ * Returns the currently active feature level.
+ * @return currently active feature level
+ * @see getSupportedFeatureLevel
+ * @see setActiveFeatureLevel
+ */
+ FeatureLevel getActiveFeatureLevel() const noexcept;
+
+
/**
* @return EntityManager used by filament
*/
diff --git a/filament/include/filament/RenderableManager.h b/filament/include/filament/RenderableManager.h
index bf523c8e60..d046f8c35b 100644
--- a/filament/include/filament/RenderableManager.h
+++ b/filament/include/filament/RenderableManager.h
@@ -40,7 +40,6 @@ namespace filament {
class BufferObject;
class Engine;
class IndexBuffer;
-class Material;
class MaterialInstance;
class Renderer;
class SkinningBuffer;
@@ -161,10 +160,17 @@ public:
/**
* Binds a material instance to the specified primitive.
*
- * If no material is specified for a given primitive, Filament will fall back to a basic default material.
+ * If no material is specified for a given primitive, Filament will fall back to a basic
+ * default material.
*
- * @param index zero-based index of the primitive, must be less than the count passed to Builder constructor
+ * The MaterialInstance's material must have a feature level equal or lower to the engine's
+ * selected feature level.
+ *
+ * @param index zero-based index of the primitive, must be less than the count passed to
+ * Builder constructor
* @param materialInstance the material to bind
+ *
+ * @see Engine::setActiveFeatureLevel
*/
Builder& material(size_t index, MaterialInstance const* materialInstance) noexcept;
@@ -590,10 +596,17 @@ public:
/**
* Changes the material instance binding for the given primitive.
*
- * \see Builder::material()
+ * The MaterialInstance's material must have a feature level equal or lower to the engine's
+ * selected feature level.
+ *
+ * @exception utils::PreConditionPanic if the engine doesn't support the material's
+ * feature level.
+ *
+ * @see Builder::material()
+ * @see Engine::setActiveFeatureLevel
*/
void setMaterialInstanceAt(Instance instance,
- size_t primitiveIndex, MaterialInstance const* materialInstance) noexcept;
+ size_t primitiveIndex, MaterialInstance const* materialInstance);
/**
* Retrieves the material instance that is bound to the given primitive.
diff --git a/filament/src/Engine.cpp b/filament/src/Engine.cpp
index b4dbf4e12c..b507b176fc 100644
--- a/filament/src/Engine.cpp
+++ b/filament/src/Engine.cpp
@@ -261,4 +261,16 @@ bool Engine::isAutomaticInstancingEnabled() const noexcept {
return upcast(this)->isAutomaticInstancingEnabled();
}
+FeatureLevel Engine::getSupportedFeatureLevel() const noexcept {
+ return upcast(this)->getSupportedFeatureLevel();
+}
+
+FeatureLevel Engine::setActiveFeatureLevel(FeatureLevel featureLevel) {
+ return upcast(this)->setActiveFeatureLevel(featureLevel);
+}
+
+FeatureLevel Engine::getActiveFeatureLevel() const noexcept {
+ return upcast(this)->getActiveFeatureLevel();
+}
+
} // namespace filament
diff --git a/filament/src/MaterialParser.cpp b/filament/src/MaterialParser.cpp
index caeaea77fc..fd784d9c7b 100644
--- a/filament/src/MaterialParser.cpp
+++ b/filament/src/MaterialParser.cpp
@@ -118,6 +118,10 @@ bool MaterialParser::getMaterialVersion(uint32_t* value) const noexcept {
return mImpl.getFromSimpleChunk(ChunkType::MaterialVersion, value);
}
+bool MaterialParser::getFeatureLevel(uint8_t* value) const noexcept {
+ return mImpl.getFromSimpleChunk(ChunkType::MaterialFeatureLevel, value);
+}
+
bool MaterialParser::getName(utils::CString* cstring) const noexcept {
ChunkType type = ChunkType::MaterialName;
const uint8_t* start = mImpl.mChunkContainer.getChunkStart(type);
diff --git a/filament/src/MaterialParser.h b/filament/src/MaterialParser.h
index 20f4fbbda5..7d7639bed9 100644
--- a/filament/src/MaterialParser.h
+++ b/filament/src/MaterialParser.h
@@ -58,6 +58,7 @@ public:
// Accessors
bool getMaterialVersion(uint32_t* value) const noexcept;
+ bool getFeatureLevel(uint8_t* value) const noexcept;
bool getName(utils::CString*) const noexcept;
bool getUIB(UniformInterfaceBlock* uib) const noexcept;
bool getSIB(SamplerInterfaceBlock* sib) const noexcept;
diff --git a/filament/src/RenderableManager.cpp b/filament/src/RenderableManager.cpp
index c99eab986a..8df03b8862 100644
--- a/filament/src/RenderableManager.cpp
+++ b/filament/src/RenderableManager.cpp
@@ -89,7 +89,7 @@ size_t RenderableManager::getPrimitiveCount(Instance instance) const noexcept {
}
void RenderableManager::setMaterialInstanceAt(Instance instance,
- size_t primitiveIndex, MaterialInstance const* materialInstance) noexcept {
+ size_t primitiveIndex, MaterialInstance const* materialInstance) {
upcast(this)->setMaterialInstanceAt(instance, 0, primitiveIndex, upcast(materialInstance));
}
diff --git a/filament/src/components/RenderableManager.cpp b/filament/src/components/RenderableManager.cpp
index b200b9027c..f7dd6946fd 100644
--- a/filament/src/components/RenderableManager.cpp
+++ b/filament/src/components/RenderableManager.cpp
@@ -251,6 +251,11 @@ RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& eng
continue;
}
+ // we want a feature level violation to be a hard error (exception if enabled, or crash)
+ ASSERT_PRECONDITION(upcast(engine).hasFeatureLevel(material->getFeatureLevel()),
+ "Material \"%s\" has feature level %u which is not supported by this Engine",
+ material->getName().c_str_safe(), (uint8_t)material->getFeatureLevel());
+
// reject invalid geometry parameters
ASSERT_PRECONDITION(entry.offset + entry.count <= entry.indices->getIndexCount(),
"[entity=%u, primitive @ %u] offset (%u) + count (%u) > indexCount (%u)",
@@ -514,12 +519,20 @@ void FRenderableManager::destroyComponentMorphTargets(FEngine& engine,
}
void FRenderableManager::setMaterialInstanceAt(Instance instance, uint8_t level,
- size_t primitiveIndex, FMaterialInstance const* mi) noexcept {
+ size_t primitiveIndex, FMaterialInstance const* mi) {
if (instance) {
Slice& primitives = getRenderPrimitives(instance, level);
if (primitiveIndex < primitives.size()) {
- primitives[primitiveIndex].setMaterialInstance(upcast(mi));
- AttributeBitset required = mi->getMaterial()->getRequiredAttributes();
+ assert_invariant(mi);
+ FMaterial const* material = mi->getMaterial();
+
+ // we want a feature level violation to be a hard error (exception if enabled, or crash)
+ ASSERT_PRECONDITION(mEngine.hasFeatureLevel(material->getFeatureLevel()),
+ "Material \"%s\" has feature level %u which is not supported by this Engine",
+ material->getName().c_str_safe(), (uint8_t)material->getFeatureLevel());
+
+ primitives[primitiveIndex].setMaterialInstance(mi);
+ AttributeBitset required = material->getRequiredAttributes();
AttributeBitset declared = primitives[primitiveIndex].getEnabledAttributes();
if (UTILS_UNLIKELY((declared & required) != required)) {
slog.w << "[instance=" << instance.asValue() << ", primitive @ " << primitiveIndex
diff --git a/filament/src/components/RenderableManager.h b/filament/src/components/RenderableManager.h
index 78c613d7ad..070fca25e0 100644
--- a/filament/src/components/RenderableManager.h
+++ b/filament/src/components/RenderableManager.h
@@ -161,7 +161,7 @@ public:
inline size_t getLevelCount(Instance) const noexcept { return 1; }
inline size_t getPrimitiveCount(Instance instance, uint8_t level) const noexcept;
void setMaterialInstanceAt(Instance instance, uint8_t level,
- size_t primitiveIndex, FMaterialInstance const* materialInstance) noexcept;
+ size_t primitiveIndex, FMaterialInstance const* materialInstance);
MaterialInstance* getMaterialInstanceAt(Instance instance, uint8_t level, size_t primitiveIndex) const noexcept;
void setGeometryAt(Instance instance, uint8_t level, size_t primitiveIndex,
PrimitiveType type, FVertexBuffer* vertices, FIndexBuffer* indices,
diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp
index 404439950d..782fe9a51c 100644
--- a/filament/src/details/Engine.cpp
+++ b/filament/src/details/Engine.cpp
@@ -264,6 +264,8 @@ void FEngine::init() {
DriverApi& driverApi = getDriverApi();
+ slog.i << "FEngine feature level: " << int(driverApi.getFeatureLevel()) << io::endl;
+
mResourceAllocator = new ResourceAllocator(driverApi);
mFullScreenTriangleVb = upcast(VertexBuffer::Builder()
@@ -588,7 +590,7 @@ int FEngine::loop() {
mPlatform = DefaultPlatform::create(&mBackend);
mOwnPlatform = true;
const char* const backend = backendToString(mBackend);
- slog.d << "FEngine resolved backend: " << backend << io::endl;
+ slog.i << "FEngine resolved backend: " << backend << io::endl;
if (mPlatform == nullptr) {
slog.e << "Selected backend not supported in this build." << io::endl;
mDriverBarrier.latch();
@@ -1059,4 +1061,15 @@ void FEngine::destroy(FEngine* engine) {
}
}
+Engine::FeatureLevel FEngine::getSupportedFeatureLevel() const noexcept {
+ FEngine::DriverApi& driver = const_cast(this)->getDriverApi();
+ return driver.getFeatureLevel();
+}
+
+Engine::FeatureLevel FEngine::setActiveFeatureLevel(FeatureLevel featureLevel) {
+ ASSERT_PRECONDITION(featureLevel <= getSupportedFeatureLevel(),
+ "Feature level %u not supported", (unsigned)featureLevel);
+ return (mActiveFeatureLevel = std::max(mActiveFeatureLevel, featureLevel));
+}
+
} // namespace filament
diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h
index 14014ae1fe..be0c876784 100644
--- a/filament/src/details/Engine.h
+++ b/filament/src/details/Engine.h
@@ -175,6 +175,14 @@ public:
return mUvFromClipMatrix;
}
+ FeatureLevel getSupportedFeatureLevel() const noexcept;
+
+ FeatureLevel setActiveFeatureLevel(FeatureLevel featureLevel);
+
+ FeatureLevel getActiveFeatureLevel() const noexcept {
+ return mActiveFeatureLevel;
+ }
+
PostProcessManager const& getPostProcessManager() const noexcept {
return mPostProcessManager;
}
@@ -362,6 +370,10 @@ public:
size_t getRequestedDriverHandleArenaSize() const noexcept { return mConfig.driverHandleArenaSizeMB * MiB; }
Config const& getConfig() const noexcept { return mConfig; }
+ bool hasFeatureLevel(backend::FeatureLevel featureLevel) const noexcept {
+ return featureLevel <= mActiveFeatureLevel;
+ }
+
private:
static Config validateConfig(const Config* pConfig) noexcept;
@@ -388,6 +400,7 @@ private:
backend::Handle mDefaultRenderTarget;
Backend mBackend;
+ FeatureLevel mActiveFeatureLevel = FeatureLevel::FEATURE_LEVEL_1;
Platform* mPlatform = nullptr;
bool mOwnPlatform = false;
bool mAutomaticInstancingEnabled = false;
diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp
index 422a12d327..fbb08aca4e 100644
--- a/filament/src/details/Material.cpp
+++ b/filament/src/details/Material.cpp
@@ -45,12 +45,13 @@ namespace filament {
using namespace backend;
static MaterialParser* createParser(Backend backend, const void* data, size_t size) {
- MaterialParser* materialParser = new MaterialParser(backend, data, size);
+ // unique_ptr so we don't leak MaterialParser on failures below
+ auto materialParser = std::make_unique(backend, data, size);
MaterialParser::ParseResult materialResult = materialParser->parse();
if (backend == Backend::NOOP) {
- return materialParser;
+ return materialParser.release();
}
ASSERT_PRECONDITION(materialResult != MaterialParser::ParseResult::ERROR_MISSING_BACKEND,
@@ -61,12 +62,12 @@ static MaterialParser* createParser(Backend backend, const void* data, size_t si
uint32_t version = 0;
materialParser->getMaterialVersion(&version);
- ASSERT_PRECONDITION(version == MATERIAL_VERSION, "Material version mismatch. Expected %d but "
- "received %d.", MATERIAL_VERSION, version);
+ ASSERT_PRECONDITION(version == MATERIAL_VERSION,
+ "Material version mismatch. Expected %d but received %d.", MATERIAL_VERSION, version);
assert_invariant(backend != Backend::DEFAULT && "Default backend has not been resolved.");
- return materialParser;
+ return materialParser.release();
}
struct Material::BuilderDetails {
@@ -95,8 +96,12 @@ Material::Builder& Material::Builder::package(const void* payload, size_t size)
}
Material* Material::Builder::build(Engine& engine) {
- MaterialParser* materialParser = createParser(
- upcast(engine).getBackend(), mImpl->mPayload, mImpl->mSize);
+ std::unique_ptr materialParser{ createParser(
+ upcast(engine).getBackend(), mImpl->mPayload, mImpl->mSize) };
+
+ if (materialParser == nullptr) {
+ return nullptr;
+ }
uint32_t v = 0;
materialParser->getShaderModels(&v);
@@ -117,7 +122,7 @@ Material* Material::Builder::build(Engine& engine) {
return nullptr;
}
- mImpl->mMaterialParser = materialParser;
+ mImpl->mMaterialParser = materialParser.release();
return upcast(engine).createMaterial(*this);
}
@@ -150,6 +155,17 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
UTILS_UNUSED_IN_RELEASE bool nameOk = parser->getName(&mName);
assert_invariant(nameOk);
+ uint8_t featureLevel = 1;
+ parser->getFeatureLevel(&featureLevel);
+ assert_invariant(featureLevel <= 2);
+ mFeatureLevel = [featureLevel]() -> FeatureLevel {
+ switch (featureLevel) {
+ default:
+ case 1: return FeatureLevel::FEATURE_LEVEL_1;
+ case 2: return FeatureLevel::FEATURE_LEVEL_2;
+ }
+ }();
+
UTILS_UNUSED_IN_RELEASE bool sibOK = parser->getSIB(&mSamplerInterfaceBlock);
assert_invariant(sibOK);
@@ -342,6 +358,7 @@ UniformInterfaceBlock::UniformInfo const* FMaterial::reflect(
}
void FMaterial::prepareProgramSlow(Variant variant) const noexcept {
+ assert_invariant(mEngine.hasFeatureLevel(mFeatureLevel));
switch (getMaterialDomain()) {
case MaterialDomain::SURFACE:
getSurfaceProgramSlow(variant);
diff --git a/filament/src/details/Material.h b/filament/src/details/Material.h
index 5cce082316..4aec4ad7e3 100644
--- a/filament/src/details/Material.h
+++ b/filament/src/details/Material.h
@@ -105,6 +105,7 @@ public:
bool isVariantLit() const noexcept { return mIsVariantLit; }
const utils::CString& getName() const noexcept { return mName; }
+ backend::FeatureLevel getFeatureLevel() const noexcept { return mFeatureLevel; }
backend::RasterState getRasterState() const noexcept { return mRasterState; }
uint32_t getId() const noexcept { return mMaterialId; }
@@ -196,6 +197,7 @@ private:
BlendingMode mRenderBlendingMode = BlendingMode::OPAQUE;
TransparencyMode mTransparencyMode = TransparencyMode::DEFAULT;
bool mIsVariantLit = false;
+ backend::FeatureLevel mFeatureLevel = backend::FeatureLevel::FEATURE_LEVEL_1;
Shading mShading = Shading::UNLIT;
BlendingMode mBlendingMode = BlendingMode::OPAQUE;
diff --git a/filament/src/details/Texture.cpp b/filament/src/details/Texture.cpp
index bb752ef62c..eb8d9af22b 100644
--- a/filament/src/details/Texture.cpp
+++ b/filament/src/details/Texture.cpp
@@ -208,7 +208,7 @@ void FTexture::setImage(FEngine& engine, size_t level,
uint32_t width, uint32_t height, uint32_t depth,
FTexture::PixelBufferDescriptor&& buffer) const {
- auto validateTarget = [](SamplerType sampler) -> bool {
+ auto validateTarget = [&engine](SamplerType sampler) -> bool {
switch (sampler) {
case SamplerType::SAMPLER_2D:
case SamplerType::SAMPLER_3D:
@@ -218,7 +218,7 @@ void FTexture::setImage(FEngine& engine, size_t level,
case SamplerType::SAMPLER_EXTERNAL:
return false;
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
- return false; // feature level 2
+ return engine.hasFeatureLevel(FeatureLevel::FEATURE_LEVEL_2);
}
};
@@ -254,19 +254,15 @@ void FTexture::setImage(FEngine& engine, size_t level,
// can't happen by construction, fallthrough...
case SamplerType::SAMPLER_2D:
assert_invariant(mDepth == 1);
- textureDepthOrLayers = mDepth;
+ textureDepthOrLayers = 1;
break;
case SamplerType::SAMPLER_3D:
textureDepthOrLayers = valueForLevel(level, mDepth);
break;
case SamplerType::SAMPLER_2D_ARRAY:
- textureDepthOrLayers = mDepth;
- break;
case SamplerType::SAMPLER_CUBEMAP:
- textureDepthOrLayers = 6;
- break;
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
- textureDepthOrLayers = mDepth * 6;
+ textureDepthOrLayers = mDepth;
break;
}
@@ -315,14 +311,27 @@ void FTexture::setImage(FEngine& engine, size_t level,
assert_invariant(w == h);
const size_t faceSize = PixelBufferDescriptor::computeDataSize(buffer.format, buffer.type,
buffer.stride ? buffer.stride : w, h, buffer.alignment);
- UTILS_NOUNROLL
- for (size_t face = 0; face < 6; face++) {
- engine.getDriverApi().update3DImage(mHandle, uint8_t(level), 0, 0, face, w, h, 1, {
- (char*)buffer.buffer + faceOffsets[face],
- faceSize, buffer.format, buffer.type, buffer.alignment,
- buffer.left, buffer.top, buffer.stride });
+
+ if (faceOffsets[0] == 0 &&
+ faceOffsets[1] == 1 * faceSize &&
+ faceOffsets[2] == 2 * faceSize &&
+ faceOffsets[3] == 3 * faceSize &&
+ faceOffsets[4] == 4 * faceSize &&
+ faceOffsets[5] == 5 * faceSize) {
+ // in this special case, we can upload all 6 faces in one call
+ engine.getDriverApi().update3DImage(mHandle, uint8_t(level),
+ 0, 0, 0, w, h, 6, std::move(buffer));
+ } else {
+ UTILS_NOUNROLL
+ for (size_t face = 0; face < 6; face++) {
+ engine.getDriverApi().update3DImage(mHandle, uint8_t(level), 0, 0, face, w, h, 1, {
+ (char*)buffer.buffer + faceOffsets[face],
+ faceSize, buffer.format, buffer.type, buffer.alignment,
+ buffer.left, buffer.top, buffer.stride });
+ }
+ engine.getDriverApi().queueCommand(
+ make_copyable_function([buffer = std::move(buffer)]() {}));
}
- engine.getDriverApi().queueCommand(make_copyable_function([buffer = std::move(buffer)]() {}));
}
void FTexture::setExternalImage(FEngine& engine, void* image) noexcept {
diff --git a/libs/filabridge/include/filament/MaterialChunkType.h b/libs/filabridge/include/filament/MaterialChunkType.h
index 3917241796..b46657ed8e 100644
--- a/libs/filabridge/include/filament/MaterialChunkType.h
+++ b/libs/filabridge/include/filament/MaterialChunkType.h
@@ -50,6 +50,7 @@ enum UTILS_PUBLIC ChunkType : uint64_t {
MaterialName = charTo64bitNum("MAT_NAME"),
MaterialVersion = charTo64bitNum("MAT_VERS"),
+ MaterialFeatureLevel = charTo64bitNum("MAT_FEAT"),
MaterialShading = charTo64bitNum("MAT_SHAD"),
MaterialBlendingMode = charTo64bitNum("MAT_BLEN"),
MaterialTransparencyMode = charTo64bitNum("MAT_TRMD"),
diff --git a/libs/filamat/include/filamat/MaterialBuilder.h b/libs/filamat/include/filamat/MaterialBuilder.h
index 67e8b43495..e82de09149 100644
--- a/libs/filamat/include/filamat/MaterialBuilder.h
+++ b/libs/filamat/include/filamat/MaterialBuilder.h
@@ -206,6 +206,7 @@ public:
using SamplerFormat = filament::backend::SamplerFormat;
using ParameterPrecision = filament::backend::Precision;
using CullingMode = filament::backend::CullingMode;
+ using FeatureLevel = filament::backend::FeatureLevel;
enum class VariableQualifier : uint8_t {
OUT
@@ -360,6 +361,8 @@ public:
MaterialBuilder& quality(ShaderQuality quality) noexcept;
+ MaterialBuilder& featureLevel(FeatureLevel featureLevel) noexcept;
+
//! Set the blending mode for this material.
MaterialBuilder& blending(BlendingMode blending) noexcept;
@@ -654,7 +657,7 @@ private:
// and vertex shaders in mProperties.
bool findProperties(filament::backend::ShaderType type,
MaterialBuilder::PropertyList& p) noexcept;
- bool runSemanticAnalysis() noexcept;
+ bool runSemanticAnalysis(MaterialInfo const& info) noexcept;
bool checkLiteRequirements() noexcept;
@@ -709,6 +712,7 @@ private:
OutputList mOutputs;
ShaderQuality mShaderQuality = ShaderQuality::DEFAULT;
+ FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1;
BlendingMode mBlendingMode = BlendingMode::OPAQUE;
BlendingMode mPostLightingBlendingMode = BlendingMode::TRANSPARENT;
CullingMode mCullingMode = CullingMode::BACK;
diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp
index 4fb67b38b7..51533bab35 100644
--- a/libs/filamat/src/MaterialBuilder.cpp
+++ b/libs/filamat/src/MaterialBuilder.cpp
@@ -271,6 +271,11 @@ MaterialBuilder& MaterialBuilder::quality(ShaderQuality quality) noexcept {
return *this;
}
+MaterialBuilder& MaterialBuilder::featureLevel(FeatureLevel featureLevel) noexcept {
+ mFeatureLevel = featureLevel;
+ return *this;
+}
+
MaterialBuilder& MaterialBuilder::blending(BlendingMode blending) noexcept {
mBlendingMode = blending;
return *this;
@@ -499,6 +504,7 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept {
info.useLegacyMorphing = mUseLegacyMorphing;
info.instanced = mInstanced;
info.vertexDomainDeviceJittered = mVertexDomainDeviceJittered;
+ info.featureLevel = mFeatureLevel;
}
bool MaterialBuilder::findProperties(filament::backend::ShaderType type,
@@ -546,7 +552,7 @@ bool MaterialBuilder::findAllProperties() noexcept {
#endif
}
-bool MaterialBuilder::runSemanticAnalysis() noexcept {
+bool MaterialBuilder::runSemanticAnalysis(MaterialInfo const& info) noexcept {
#ifndef FILAMAT_LITE
using namespace filament::backend;
GLSLTools glslTools;
@@ -561,12 +567,12 @@ bool MaterialBuilder::runSemanticAnalysis() noexcept {
ShaderModel model = static_cast(mSemanticCodeGenParams.shaderModel);
std::string shaderCode = peek(ShaderType::VERTEX, mSemanticCodeGenParams, mProperties);
- bool result = glslTools.analyzeVertexShader(shaderCode, model, mMaterialDomain, targetApi);
+ bool result = glslTools.analyzeVertexShader(shaderCode, model, mMaterialDomain, targetApi, info);
if (!result) return false;
shaderCode = peek(ShaderType::FRAGMENT, mSemanticCodeGenParams, mProperties);
result = glslTools.analyzeFragmentShader(shaderCode, model, mMaterialDomain, targetApi,
- mCustomSurfaceShading);
+ mCustomSurfaceShading, info);
return result;
#else
return true;
@@ -953,12 +959,12 @@ Package MaterialBuilder::build(JobSystem& jobSystem) noexcept {
}
// prepareToBuild must be called first, to populate mCodeGenPermutations.
- MaterialInfo info {};
+ MaterialInfo info{};
prepareToBuild(info);
// Run checks, in order.
// The call to findProperties populates mProperties and must come before runSemanticAnalysis.
- if (!checkLiteRequirements() || !findAllProperties() || !runSemanticAnalysis()) {
+ if (!checkLiteRequirements() || !findAllProperties() || !runSemanticAnalysis(info)) {
// Return an empty package to signal a failure to build the material.
return Package::invalidPackage();
}
@@ -1039,6 +1045,7 @@ std::string MaterialBuilder::peek(filament::backend::ShaderType type,
void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& info) const noexcept {
container.addSimpleChild(ChunkType::MaterialVersion, filament::MATERIAL_VERSION);
+ container.addSimpleChild(ChunkType::MaterialFeatureLevel, (uint8_t)mFeatureLevel);
container.addSimpleChild(ChunkType::MaterialName, mMaterialName.c_str_safe());
container.addSimpleChild(ChunkType::MaterialShaderModels, mShaderModels.getValue());
container.addSimpleChild(ChunkType::MaterialDomain, static_cast(mMaterialDomain));
diff --git a/libs/filamat/src/sca/GLSLTools.cpp b/libs/filamat/src/sca/GLSLTools.cpp
index 2b9459c381..4421753ded 100644
--- a/libs/filamat/src/sca/GLSLTools.cpp
+++ b/libs/filamat/src/sca/GLSLTools.cpp
@@ -22,6 +22,7 @@
#include
#include
#include
+#include "../shaders/MaterialInfo.h"
#include
@@ -53,7 +54,8 @@ GLSLangCleaner::~GLSLangCleaner() {
bool GLSLTools::analyzeFragmentShader(const std::string& shaderCode, ShaderModel model,
MaterialBuilder::MaterialDomain materialDomain,
- MaterialBuilder::TargetApi targetApi, bool hasCustomSurfaceShading) const noexcept {
+ MaterialBuilder::TargetApi targetApi, bool hasCustomSurfaceShading,
+ MaterialInfo const& info) const noexcept {
// Parse to check syntax and semantic.
const char* shaderCString = shaderCode.c_str();
@@ -90,6 +92,18 @@ bool GLSLTools::analyzeFragmentShader(const std::string& shaderCode, ShaderModel
return false;
}
+ switch (info.featureLevel) {
+ case FeatureLevel::FEATURE_LEVEL_1:
+ // TODO: feature level checks
+ // - check no more than 9 samplers are used by the user and 16 total
+ // - check cubemap arrays are not used
+ break;
+ case FeatureLevel::FEATURE_LEVEL_2:
+ // TODO: feature level checks
+ // - check no more than 12 samplers are used by the user and 31 total
+ break;
+ }
+
// If this is a post-process material, at this point we've successfully met all the
// requirements.
if (materialDomain == MaterialBuilder::MaterialDomain::POST_PROCESS) {
@@ -129,7 +143,8 @@ bool GLSLTools::analyzeFragmentShader(const std::string& shaderCode, ShaderModel
bool GLSLTools::analyzeVertexShader(const std::string& shaderCode, ShaderModel model,
MaterialBuilder::MaterialDomain materialDomain,
- MaterialBuilder::TargetApi targetApi) const noexcept {
+ MaterialBuilder::TargetApi targetApi,
+ MaterialInfo const& info) const noexcept {
// TODO: After implementing post-process vertex shaders, properly analyze them here.
if (materialDomain == MaterialBuilder::MaterialDomain::POST_PROCESS) {
@@ -161,6 +176,18 @@ bool GLSLTools::analyzeVertexShader(const std::string& shaderCode, ShaderModel m
return false;
}
+ switch (info.featureLevel) {
+ case FeatureLevel::FEATURE_LEVEL_1:
+ // TODO: feature level checks
+ // - check no more than 9 samplers are used by the user and 16 total
+ // - check cubemap arrays are not used
+ break;
+ case FeatureLevel::FEATURE_LEVEL_2:
+ // TODO: feature level checks
+ // - check no more than 12 samplers are used by the user and 31 total
+ break;
+ }
+
return true;
}
diff --git a/libs/filamat/src/sca/GLSLTools.h b/libs/filamat/src/sca/GLSLTools.h
index 0e8cc14f69..c14200e987 100644
--- a/libs/filamat/src/sca/GLSLTools.h
+++ b/libs/filamat/src/sca/GLSLTools.h
@@ -124,12 +124,14 @@ public:
bool analyzeFragmentShader(const std::string& shaderCode,
filament::backend::ShaderModel model,
MaterialBuilder::MaterialDomain materialDomain,
- MaterialBuilder::TargetApi targetApi, bool hasCustomSurfaceShading) const noexcept;
+ MaterialBuilder::TargetApi targetApi, bool hasCustomSurfaceShading,
+ MaterialInfo const& info) const noexcept;
bool analyzeVertexShader(const std::string& shaderCode,
filament::backend::ShaderModel model,
MaterialBuilder::MaterialDomain materialDomain,
- MaterialBuilder::TargetApi targetApi) const noexcept;
+ MaterialBuilder::TargetApi targetApi,
+ MaterialInfo const& info) const noexcept;
// Public for unit tests.
using Property = MaterialBuilder::Property;
diff --git a/libs/filamat/src/shaders/CodeGenerator.cpp b/libs/filamat/src/shaders/CodeGenerator.cpp
index c73cfebee2..1d3f46f64f 100644
--- a/libs/filamat/src/shaders/CodeGenerator.cpp
+++ b/libs/filamat/src/shaders/CodeGenerator.cpp
@@ -16,6 +16,8 @@
#include "CodeGenerator.h"
+#include "MaterialInfo.h"
+
#include "generated/shaders.h"
#include
@@ -37,25 +39,27 @@ io::sstream& CodeGenerator::generateSeparator(io::sstream& out) {
return out;
}
-io::sstream& CodeGenerator::generateProlog(io::sstream& out, ShaderType type,
- bool hasExternalSamplers) const {
+utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, ShaderType type,
+ MaterialInfo const& info) const {
switch (mShaderModel) {
case ShaderModel::MOBILE:
// Vulkan requires version 310 or higher
- if (mTargetLanguage == TargetLanguage::SPIRV) {
+ if (mTargetLanguage == TargetLanguage::SPIRV ||
+ info.featureLevel >= FeatureLevel::FEATURE_LEVEL_2) {
// Vulkan requires layout locations on ins and outs, which were not supported
// in the OpenGL 4.1 GLSL profile.
out << "#version 310 es\n\n";
} else {
out << "#version 300 es\n\n";
}
- if (hasExternalSamplers) {
+ if (info.hasExternalSamplers) {
out << "#extension GL_OES_EGL_image_external_essl3 : require\n\n";
}
out << "#define TARGET_MOBILE\n";
break;
case ShaderModel::DESKTOP:
- if (mTargetLanguage == TargetLanguage::SPIRV) {
+ if (mTargetLanguage == TargetLanguage::SPIRV ||
+ info.featureLevel >= FeatureLevel::FEATURE_LEVEL_2) {
// Vulkan requires binding specifiers on uniforms and samplers, which were not
// supported in the OpenGL 4.1 GLSL profile.
out << "#version 450 core\n\n";
@@ -91,10 +95,17 @@ io::sstream& CodeGenerator::generateProlog(io::sstream& out, ShaderType type,
out << "#define FILAMENT_OPENGL_SEMANTICS\n";
}
+ out << '\n';
+ out << "#define FILAMENT_FEATURE_LEVEL_1 1\n";
+ out << "#define FILAMENT_FEATURE_LEVEL_2 2\n";
+ out << "#define FILAMENT_FEATURE_LEVEL FILAMENT_FEATURE_LEVEL_" << (int)info.featureLevel << "\n";
+
+
out << '\n';
if (mTargetApi == TargetApi::VULKAN ||
mTargetApi == TargetApi::METAL ||
- (mTargetApi == TargetApi::OPENGL && mShaderModel == ShaderModel::DESKTOP)) {
+ (mTargetApi == TargetApi::OPENGL && mShaderModel == ShaderModel::DESKTOP) ||
+ info.featureLevel >= FeatureLevel::FEATURE_LEVEL_2) {
out << "#define FILAMENT_HAS_FEATURE_TEXTURE_GATHER\n";
}
diff --git a/libs/filamat/src/shaders/CodeGenerator.h b/libs/filamat/src/shaders/CodeGenerator.h
index 465a692bba..3c002f4dde 100644
--- a/libs/filamat/src/shaders/CodeGenerator.h
+++ b/libs/filamat/src/shaders/CodeGenerator.h
@@ -56,31 +56,32 @@ public:
filament::backend::ShaderModel getShaderModel() const noexcept { return mShaderModel; }
// insert a separator (can be a new line)
- static utils::io::sstream& generateSeparator(utils::io::sstream& out) ;
+ static utils::io::sstream& generateSeparator(utils::io::sstream& out);
// generate prolog for the given shader
- utils::io::sstream& generateProlog(utils::io::sstream& out, ShaderType type, bool hasExternalSamplers) const;
+ utils::io::sstream& generateProlog(utils::io::sstream& out, ShaderType type,
+ MaterialInfo const& info) const;
- static utils::io::sstream& generateEpilog(utils::io::sstream& out) ;
+ static utils::io::sstream& generateEpilog(utils::io::sstream& out);
// generate common functions for the given shader
- static utils::io::sstream& generateCommon(utils::io::sstream& out, ShaderType type) ;
- static utils::io::sstream& generatePostProcessCommon(utils::io::sstream& out, ShaderType type) ;
- static utils::io::sstream& generateCommonMaterial(utils::io::sstream& out, ShaderType type) ;
+ static utils::io::sstream& generateCommon(utils::io::sstream& out, ShaderType type);
+ static utils::io::sstream& generatePostProcessCommon(utils::io::sstream& out, ShaderType type);
+ static utils::io::sstream& generateCommonMaterial(utils::io::sstream& out, ShaderType type);
- static utils::io::sstream& generateFog(utils::io::sstream& out, ShaderType type) ;
+ static utils::io::sstream& generateFog(utils::io::sstream& out, ShaderType type);
// generate the shader's main()
- static utils::io::sstream& generateShaderMain(utils::io::sstream& out, ShaderType type) ;
- static utils::io::sstream& generatePostProcessMain(utils::io::sstream& out, ShaderType type) ;
+ static utils::io::sstream& generateShaderMain(utils::io::sstream& out, ShaderType type);
+ static utils::io::sstream& generatePostProcessMain(utils::io::sstream& out, ShaderType type);
// generate the shader's code for the lit shading model
static utils::io::sstream& generateShaderLit(utils::io::sstream& out, ShaderType type,
- filament::Variant variant, filament::Shading shading, bool customSurfaceShading) ;
+ filament::Variant variant, filament::Shading shading, bool customSurfaceShading);
// generate the shader's code for the unlit shading model
static utils::io::sstream& generateShaderUnlit(utils::io::sstream& out, ShaderType type,
- filament::Variant variant, bool hasShadowMultiplier) ;
+ filament::Variant variant, bool hasShadowMultiplier);
// generate the shader's code for the screen-space reflections
static utils::io::sstream& generateShaderReflections(utils::io::sstream& out, ShaderType type,
@@ -88,12 +89,12 @@ public:
// generate declarations for custom interpolants
static utils::io::sstream& generateVariable(utils::io::sstream& out, ShaderType type,
- const utils::CString& name, size_t index) ;
+ const utils::CString& name, size_t index);
// generate declarations for non-custom "in" variables
static utils::io::sstream& generateShaderInputs(utils::io::sstream& out, ShaderType type,
- const filament::AttributeBitset& attributes, filament::Interpolation interpolation) ;
- static utils::io::sstream& generatePostProcessInputs(utils::io::sstream& out, ShaderType type) ;
+ const filament::AttributeBitset& attributes, filament::Interpolation interpolation);
+ static utils::io::sstream& generatePostProcessInputs(utils::io::sstream& out, ShaderType type);
// generate declarations for custom output variables
utils::io::sstream& generateOutput(utils::io::sstream& out, ShaderType type,
@@ -102,7 +103,7 @@ public:
MaterialBuilder::OutputType outputType) const;
// generate no-op shader for depth prepass
- static utils::io::sstream& generateDepthShaderMain(utils::io::sstream& out, ShaderType type) ;
+ static utils::io::sstream& generateDepthShaderMain(utils::io::sstream& out, ShaderType type);
// generate uniforms
utils::io::sstream& generateUniforms(utils::io::sstream& out, ShaderType type, uint8_t binding,
@@ -114,23 +115,23 @@ public:
// generate subpass
static utils::io::sstream& generateSubpass(utils::io::sstream& out,
- filament::SubpassInfo subpass) ;
+ filament::SubpassInfo subpass);
// generate material properties getters
static utils::io::sstream& generateMaterialProperty(utils::io::sstream& out,
- MaterialBuilder::Property property, bool isSet) ;
+ MaterialBuilder::Property property, bool isSet);
utils::io::sstream& generateQualityDefine(utils::io::sstream& out, ShaderQuality quality) const;
- static utils::io::sstream& generateDefine(utils::io::sstream& out, const char* name, bool value) ;
- static utils::io::sstream& generateDefine(utils::io::sstream& out, const char* name, uint32_t value) ;
- static utils::io::sstream& generateDefine(utils::io::sstream& out, const char* name, const char* string) ;
+ static utils::io::sstream& generateDefine(utils::io::sstream& out, const char* name, bool value);
+ static utils::io::sstream& generateDefine(utils::io::sstream& out, const char* name, uint32_t value);
+ static utils::io::sstream& generateDefine(utils::io::sstream& out, const char* name, const char* string);
static utils::io::sstream& generateIndexedDefine(utils::io::sstream& out, const char* name,
- uint32_t index, uint32_t value) ;
+ uint32_t index, uint32_t value);
- static utils::io::sstream& generatePostProcessGetters(utils::io::sstream& out, ShaderType type) ;
- static utils::io::sstream& generateGetters(utils::io::sstream& out, ShaderType type) ;
- static utils::io::sstream& generateParameters(utils::io::sstream& out, ShaderType type) ;
+ static utils::io::sstream& generatePostProcessGetters(utils::io::sstream& out, ShaderType type);
+ static utils::io::sstream& generateGetters(utils::io::sstream& out, ShaderType type);
+ static utils::io::sstream& generateParameters(utils::io::sstream& out, ShaderType type);
static void fixupExternalSamplers(
std::string& shader, filament::SamplerInterfaceBlock const& sib) noexcept;
diff --git a/libs/filamat/src/shaders/MaterialInfo.h b/libs/filamat/src/shaders/MaterialInfo.h
index 8e7b796408..f292b6395d 100644
--- a/libs/filamat/src/shaders/MaterialInfo.h
+++ b/libs/filamat/src/shaders/MaterialInfo.h
@@ -61,6 +61,7 @@ struct UTILS_PUBLIC MaterialInfo {
filament::SubpassInfo subpass;
filament::SamplerBindingMap samplerBindings;
filament::ShaderQuality quality;
+ filament::backend::FeatureLevel featureLevel;
};
}
diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp
index 82c96b3c09..7569031d9d 100644
--- a/libs/filamat/src/shaders/ShaderGenerator.cpp
+++ b/libs/filamat/src/shaders/ShaderGenerator.cpp
@@ -181,7 +181,7 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel,
const CodeGenerator cg(shaderModel, targetApi, targetLanguage);
const bool lit = material.isLit;
- cg.generateProlog(vs, ShaderType::VERTEX, material.hasExternalSamplers);
+ cg.generateProlog(vs, ShaderType::VERTEX, material);
cg.generateQualityDefine(vs, material.quality);
@@ -315,7 +315,7 @@ std::string ShaderGenerator::createFragmentProgram(ShaderModel shaderModel,
const bool lit = material.isLit;
io::sstream fs;
- cg.generateProlog(fs, ShaderType::FRAGMENT, material.hasExternalSamplers);
+ cg.generateProlog(fs, ShaderType::FRAGMENT, material);
cg.generateQualityDefine(fs, material.quality);
@@ -536,7 +536,7 @@ std::string ShaderGenerator::createPostProcessVertexProgram(
const filament::Variant::type_t variantKey, SamplerBindingMap const& samplerBindingMap) const noexcept {
const CodeGenerator cg(sm, targetApi, targetLanguage);
io::sstream vs;
- cg.generateProlog(vs, ShaderType::VERTEX, false);
+ cg.generateProlog(vs, ShaderType::VERTEX, material);
cg.generateQualityDefine(vs, material.quality);
@@ -577,7 +577,7 @@ std::string ShaderGenerator::createPostProcessFragmentProgram(
uint8_t variant, const SamplerBindingMap& samplerBindingMap) const noexcept {
const CodeGenerator cg(sm, targetApi, targetLanguage);
io::sstream fs;
- cg.generateProlog(fs, ShaderType::FRAGMENT, false);
+ cg.generateProlog(fs, ShaderType::FRAGMENT, material);
cg.generateQualityDefine(fs, material.quality);
diff --git a/libs/matdbg/src/JsonWriter.cpp b/libs/matdbg/src/JsonWriter.cpp
index 2fd175d17f..13ff2d413b 100644
--- a/libs/matdbg/src/JsonWriter.cpp
+++ b/libs/matdbg/src/JsonWriter.cpp
@@ -86,6 +86,7 @@ static void printStringChunk(ostream& json, const ChunkContainer& container,
static bool printMaterial(ostream& json, const ChunkContainer& container) {
printStringChunk(json, container, MaterialName, "name");
printUint32Chunk(json, container, MaterialVersion, "version");
+ printUint32Chunk(json, container, MaterialFeatureLevel, "feature_level");
json << "\"shading\": {\n";
printChunk(json, container, MaterialShading, "model");
printChunk(json, container, ChunkType::MaterialDomain, "material_domain");
diff --git a/libs/matdbg/src/TextWriter.cpp b/libs/matdbg/src/TextWriter.cpp
index bcbff0453c..faaaa5db69 100644
--- a/libs/matdbg/src/TextWriter.cpp
+++ b/libs/matdbg/src/TextWriter.cpp
@@ -95,6 +95,12 @@ static bool printMaterial(ostream& text, const ChunkContainer& container) {
text << version << endl;
}
+ uint8_t featureLevel;
+ if (read(container, MaterialFeatureLevel, &featureLevel)) {
+ text << " " << setw(alignment) << left << "Feature level: ";
+ text << featureLevel << endl;
+ }
+
CString name;
if (read(container, MaterialName, &name)) {
text << " " << setw(alignment) << left << "Name: ";
diff --git a/tools/matc/src/matc/ParametersProcessor.cpp b/tools/matc/src/matc/ParametersProcessor.cpp
index 6feac03e6f..6e9477909e 100644
--- a/tools/matc/src/matc/ParametersProcessor.cpp
+++ b/tools/matc/src/matc/ParametersProcessor.cpp
@@ -15,6 +15,7 @@
*/
#include "ParametersProcessor.h"
+#include "backend/DriverEnums.h"
#include
#include
@@ -35,7 +36,7 @@ static bool logEnumIssue(const std::string& key, const JsonishString& value,
std::cerr << "Error while processing key '" << key << "' value." << std::endl;
std::cerr << "Value '" << value.getString() << "' is invalid. Valid values are:"
<< std::endl;
- for (auto entries : map) {
+ for (const auto& entries : map) {
std::cerr << " " << entries.first << std::endl;
}
return false;
@@ -398,6 +399,22 @@ static bool processQuality(MaterialBuilder& builder, const JsonishValue& value)
return true;
}
+static bool processFeatureLevel(MaterialBuilder& builder, const JsonishValue& value) {
+ using filament::backend::FeatureLevel;
+ JsonishNumber const* const number = value.toJsonNumber();
+ FeatureLevel featureLevel;
+ if (number->getFloat() == 1.0f) {
+ featureLevel = FeatureLevel::FEATURE_LEVEL_1;
+ } else if (number->getFloat() == 2.0f) {
+ featureLevel = FeatureLevel::FEATURE_LEVEL_2;
+ } else {
+ std::cerr << "featureLevel: invalid value " << number->getFloat() << std::endl;
+ return false;
+ }
+ builder.featureLevel(featureLevel);
+ return true;
+}
+
static bool processOutput(MaterialBuilder& builder, const JsonishObject& jsonObject) noexcept {
const JsonishValue* nameValue = jsonObject.getValue("name");
@@ -785,6 +802,7 @@ ParametersProcessor::ParametersProcessor() {
mParameters["outputs"] = { &processOutputs, Type::ARRAY };
mParameters["quality"] = { &processQuality, Type::STRING };
mParameters["customSurfaceShading"] = { &processCustomSurfaceShading, Type::BOOL };
+ mParameters["featureLevel"] = { &processFeatureLevel, Type::NUMBER };
}
bool ParametersProcessor::process(MaterialBuilder& builder, const JsonishObject& jsonObject) {
diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp
index a9d9ac9ab9..6a6418592d 100644
--- a/web/filament-js/jsbindings.cpp
+++ b/web/filament-js/jsbindings.cpp
@@ -391,6 +391,12 @@ class_("Engine")
.function("isAutomaticInstancingEnabled", &Engine::isAutomaticInstancingEnabled)
+ .function("getSupportedFeatureLevel", &Engine::getSupportedFeatureLevel)
+
+ .function("setActiveFeatureLevel", &Engine::setActiveFeatureLevel)
+
+ .function("getActiveFeatureLevel", &Engine::getActiveFeatureLevel)
+
.function("_execute", EMBIND_LAMBDA(void, (Engine* engine), {
EM_ASM_INT({
const handle = window.filament_contextHandle;
diff --git a/web/filament-js/jsenums.cpp b/web/filament-js/jsenums.cpp
index 7b62b0f303..34c3b84c11 100644
--- a/web/filament-js/jsenums.cpp
+++ b/web/filament-js/jsenums.cpp
@@ -399,6 +399,10 @@ enum_("CullingMode")
.value("BACK", backend::CullingMode::BACK)
.value("FRONT_AND_BACK", backend::CullingMode::FRONT_AND_BACK);
+enum_("FeatureLevel")
+ .value("FEATURE_LEVEL_1", backend::FeatureLevel::FEATURE_LEVEL_1)
+ .value("FEATURE_LEVEL_2", backend::FeatureLevel::FEATURE_LEVEL_2);
+
enum_("Ktx2Reader$TransferFunction")
.value("LINEAR", ktxreader::Ktx2Reader::TransferFunction::LINEAR)
.value("sRGB", ktxreader::Ktx2Reader::TransferFunction::sRGB);