From 56355231bdfa7e810242178d17da31c85678af0f Mon Sep 17 00:00:00 2001 From: Eliza Velasquez Date: Wed, 18 Oct 2023 15:36:25 -0700 Subject: [PATCH] Allow explicitly initializing at feature level 0 This change does three main things. First, it adds an option to the Engine Builder to pick the feature level at which to instantiate Filament. The only real practical purpose of allowing this is to be able to instantiate at feature level 0. Secondly, it allows feature level 0 to properly work on non-ES2 devices. Thirdly, it changes both Android and desktop hellotriangle samples to explicitly opt-in to feature level 0. Unfortunately, feature levels are used in two different, somewhat contradictory ways presently in Filament, which can make reasoning about this change a bit confusing. From a client perspective, feature levels refer to buckets of capabilities which are guaranteed to be supported. Internally, there is a separate "feature level" stored internally at the Driver subclass level which generally corresponds to the maximum supported feature level, but is also referenced when activating workarounds for limited devices. For example, Uniform Buffer Objects are not supported in ES2, however, Filament supports emulating them such that the client does not need to care at all; a supported feature is a supported feature. But internally, Filament uses this "Driver" feature level to determine whether or not a given workaround is needed. There were several cases where the "active feature level" was being examined in order to activate these workarounds rather than the "driver feature level", which was incorrect. Why should non-ES2-only devices want to activate feature level 0? Allowing this behavior 1. makes feature level 0 more consistent with the behavior of other feature levels and 2. allows clients a layer of validation that their software will work on all devices supported by Filament if they explicitly opt into it. Consistency: Filament guarantees that any given device which supports a given feature level will also support running on every feature level below, except for feature level 0. This change removes that exception. Validation: It's not perfect, and there will likely be bugs and unexpected differences in behavior between ES2 and non-ES2 devices that crop up in the future between two devices running on the same feature level. However, it's at least a basic high level layer of validation that enables more rapid testing workflows directly via desktop versions of Filament rather than having to fiddle with something like ANGLE to get perfect GLES 2.0 compliance. Additionally, it expands options for automated testing (with the same caveats). This change has been tested on both the desktop and Android versions of hellotriangle. --- NEW_RELEASE_NOTES.md | 2 ++ .../filament-android/src/main/cpp/Engine.cpp | 6 +++++ .../com/google/android/filament/Engine.java | 12 ++++++++++ .../filament/hellotriangle/MainActivity.kt | 11 +++------ filament/CMakeLists.txt | 24 ++----------------- .../backend/include/backend/DriverEnums.h | 13 ++++++++++ filament/include/filament/Engine.h | 10 +++++--- filament/src/Froxelizer.cpp | 6 ++--- filament/src/PostProcessManager.cpp | 2 +- filament/src/RendererUtils.cpp | 2 +- filament/src/details/Engine.cpp | 12 ++++++---- filament/src/details/Engine.h | 2 +- filament/src/details/Material.cpp | 11 +++++---- filament/src/details/Skybox.cpp | 5 +--- filament/src/details/View.cpp | 1 - .../materials/transparentColor.mat | 3 ++- libs/filamentapp/src/FilamentApp.cpp | 12 ++++++---- samples/hellotriangle.cpp | 1 + samples/materials/bakedColor.mat | 3 ++- 19 files changed, 78 insertions(+), 60 deletions(-) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 4a1a9c7fa7..eec3df3f3c 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -7,3 +7,5 @@ for next branch cut* header. appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut + +- 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 e5bb4e95fa..72b499e618 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -483,6 +483,12 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu builder->config(&config); } +extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderFeatureLevel( + JNIEnv*, jclass, jlong nativeBuilder, jint ordinal) { + Engine::Builder* builder = (Engine::Builder*) nativeBuilder; + builder->featureLevel((Engine::FeatureLevel)ordinal); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderSharedContext( JNIEnv*, jclass, jlong nativeBuilder, jlong sharedContext) { Engine::Builder* builder = (Engine::Builder*) nativeBuilder; 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 3423e87d05..43224036e3 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 @@ -209,6 +209,17 @@ public class Engine { return this; } + /** + * Sets the initial featureLevel for the Engine. + * + * @param featureLevel The feature level at which initialize Filament. + * @return A reference to this Builder for chaining calls. + */ + public Builder featureLevel(FeatureLevel featureLevel) { + nSetBuilderFeatureLevel(mNativeBuilder, featureLevel.ordinal()); + return this; + } + /** * Creates an instance of Engine * @@ -1149,6 +1160,7 @@ public class Engine { private static native void nSetBuilderConfig(long nativeBuilder, long commandBufferSizeMB, long perRenderPassArenaSizeMB, long driverHandleArenaSizeMB, long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount); + private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal); private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext); private static native long nBuilderBuild(long nativeBuilder); } 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 7d34a3031d..8571ee4d60 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 @@ -110,7 +110,7 @@ class MainActivity : Activity() { } private fun setupFilament() { - engine = Engine.create() + engine = Engine.Builder().featureLevel(Engine.FeatureLevel.FEATURE_LEVEL_0).build() renderer = engine.createRenderer() scene = engine.createScene() view = engine.createView() @@ -120,13 +120,8 @@ class MainActivity : Activity() { private fun setupView() { scene.skybox = Skybox.Builder().color(0.035f, 0.035f, 0.035f, 1.0f).build(engine) - if (engine.activeFeatureLevel == Engine.FeatureLevel.FEATURE_LEVEL_0) { - // post-processing is not supported at feature level 0 - view.isPostProcessingEnabled = false - } else { - // NOTE: Try to disable post-processing (tone-mapping, etc.) to see the difference - // view.isPostProcessingEnabled = false - } + // post-processing is not supported at feature level 0 + view.isPostProcessingEnabled = false // Tell the view which camera we want to use view.camera = camera diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index b65e3cca9b..8eb45b40b5 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -218,6 +218,7 @@ set(MATERIAL_SRCS src/materials/colorGrading/customResolveAsSubpass.mat src/materials/debugShadowCascades.mat src/materials/defaultMaterial.mat + src/materials/defaultMaterial0.mat src/materials/dof/dof.mat src/materials/dof/dofCoc.mat src/materials/dof/dofDownsample.mat @@ -237,6 +238,7 @@ set(MATERIAL_SRCS src/materials/ssao/bilateralBlurBentNormals.mat src/materials/ssao/mipmapDepth.mat src/materials/skybox.mat + src/materials/skybox0.mat src/materials/ssao/sao.mat src/materials/ssao/saoBentNormals.mat src/materials/separableGaussianBlur.mat @@ -245,11 +247,6 @@ set(MATERIAL_SRCS src/materials/vsmMipmap.mat ) -set(MATERIAL_ES2_SRCS - src/materials/defaultMaterial0.mat - src/materials/skybox0.mat -) - # Embed the binary resource blob for materials. get_resgen_vars(${RESOURCE_DIR} materials) list(APPEND PRIVATE_HDRS ${RESGEN_HEADER}) @@ -315,23 +312,6 @@ foreach (mat_src ${MATERIAL_SRCS}) list(APPEND MATERIAL_BINS ${output_path}) endforeach() -if (IS_MOBILE_TARGET AND FILAMENT_SUPPORTS_OPENGL) - foreach (mat_src ${MATERIAL_ES2_SRCS}) - get_filename_component(localname "${mat_src}" NAME_WE) - get_filename_component(fullname "${mat_src}" ABSOLUTE) - set(output_path "${MATERIAL_DIR}/${localname}.filamat") - - add_custom_command( - OUTPUT ${output_path} - COMMAND matc -a opengl -p ${MATC_TARGET} ${MATC_OPT_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 () -endif () - # Additional dependencies on included files for materials add_custom_command( diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index 227759769d..1dd11cd455 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -154,6 +154,19 @@ enum class ShaderLanguage { MSL = 3, }; +static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguage) { + switch (shaderLanguage) { + case ShaderLanguage::ESSL1: + return "ESSL 1.0"; + case ShaderLanguage::ESSL3: + return "ESSL 3.0"; + case ShaderLanguage::SPIRV: + return "SPIR-V"; + case ShaderLanguage::MSL: + return "MSL"; + } +} + /** * Bitmask for selecting render buffers */ diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index 80ef41856b..ed48c52846 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -172,6 +172,7 @@ public: using Platform = backend::Platform; using Backend = backend::Backend; using DriverConfig = backend::Platform::DriverConfig; + using FeatureLevel = backend::FeatureLevel; /** * Config is used to define the memory footprint used by the engine, such as the @@ -351,6 +352,12 @@ public: */ Builder& sharedContext(void* sharedContext) noexcept; + /** + * @param featureLevel The feature level at which initialize Filament. + * @return A reference to this Builder for chaining calls. + */ + Builder& featureLevel(FeatureLevel featureLevel) noexcept; + #if UTILS_HAS_THREADING /** * Creates the filament Engine asynchronously. @@ -482,9 +489,6 @@ public: */ static void destroy(Engine* engine); - using FeatureLevel = backend::FeatureLevel; - - /** * Query the feature level supported by the selected backend. * diff --git a/filament/src/Froxelizer.cpp b/filament/src/Froxelizer.cpp index 52e0d71a84..c469932c25 100644 --- a/filament/src/Froxelizer.cpp +++ b/filament/src/Froxelizer.cpp @@ -104,12 +104,12 @@ Froxelizer::Froxelizer(FEngine& engine) static_assert(std::is_same_v, "Record Buffer must use bytes"); - if (UTILS_UNLIKELY(engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0)) { + DriverApi& driverApi = engine.getDriverApi(); + + if (UTILS_UNLIKELY(driverApi.getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0)) { return; } - DriverApi& driverApi = engine.getDriverApi(); - mFroxelBufferEntryCount = std::min( FROXEL_BUFFER_MAX_ENTRY_COUNT, engine.getDriverApi().getMaxUniformBufferSize() / 16u); diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 4f176c1014..27173309ff 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -380,7 +380,7 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph // generate depth pass at the requested resolution auto& structurePass = fg.addPass("Structure Pass", [&](FrameGraph::Builder& builder, auto& data) { - bool const isES2 = mEngine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0; + bool const isES2 = mEngine.getDriverApi().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0; data.depth = builder.createTexture("Structure Buffer", { .width = width, .height = height, .levels = uint8_t(levelCount), diff --git a/filament/src/RendererUtils.cpp b/filament/src/RendererUtils.cpp index e45ce8c7d9..62e7a299ac 100644 --- a/filament/src/RendererUtils.cpp +++ b/filament/src/RendererUtils.cpp @@ -100,7 +100,7 @@ FrameGraphId RendererUtils::colorPass( "Depth/Stencil Buffer" : "Depth Buffer"; bool const isES2 = - engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0; + engine.getDriverApi().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0; TextureFormat const stencilFormat = isES2 ? TextureFormat::DEPTH24_STENCIL8 : TextureFormat::DEPTH32F_STENCIL8; diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index 84bb55f4a0..642581f005 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -67,6 +67,7 @@ struct Engine::BuilderDetails { Backend mBackend = Backend::DEFAULT; Platform* mPlatform = nullptr; Engine::Config mConfig; + FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1; void* mSharedContext = nullptr; static Config validateConfig(const Config* pConfig) noexcept; }; @@ -185,6 +186,7 @@ static const uint16_t sFullScreenTriangleIndices[3] = { 0, 1, 2 }; FEngine::FEngine(Engine::Builder const& builder) : mBackend(builder->mBackend), + mActiveFeatureLevel(builder->mFeatureLevel), mPlatform(builder->mPlatform), mSharedGLContext(builder->mSharedContext), mPostProcessManager(*this), @@ -326,15 +328,12 @@ void FEngine::init() { driverApi.update3DImage(mDummyZeroTexture, 0, 0, 0, 0, 1, 1, 1, { zeroes, 4, Texture::Format::RGBA, Texture::Type::UBYTE }); -#ifdef FILAMENT_TARGET_MOBILE if (UTILS_UNLIKELY(mActiveFeatureLevel == FeatureLevel::FEATURE_LEVEL_0)) { FMaterial::DefaultMaterialBuilder defaultMaterialBuilder; defaultMaterialBuilder.package( MATERIALS_DEFAULTMATERIAL0_DATA, MATERIALS_DEFAULTMATERIAL0_SIZE); mDefaultMaterial = downcast(defaultMaterialBuilder.build(*const_cast(this))); - } else -#endif - { + } else { mDefaultColorGrading = downcast(ColorGrading::Builder().build(*this)); FMaterial::DefaultMaterialBuilder defaultMaterialBuilder; @@ -1204,6 +1203,11 @@ Engine::Builder& Engine::Builder::config(Engine::Config const* config) noexcept return *this; } +Engine::Builder& Engine::Builder::featureLevel(FeatureLevel featureLevel) noexcept { + mImpl->mFeatureLevel = featureLevel; + return *this; +} + Engine::Builder& Engine::Builder::sharedContext(void* sharedContext) noexcept { mImpl->mSharedContext = sharedContext; return *this; diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index e7d910002d..e7c9e8d95f 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -231,7 +231,7 @@ public: default: return backend::ShaderLanguage::ESSL3; case Backend::OPENGL: - return mActiveFeatureLevel == FeatureLevel::FEATURE_LEVEL_0 + return getDriver().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0 ? backend::ShaderLanguage::ESSL1 : backend::ShaderLanguage::ESSL3; case Backend::VULKAN: return backend::ShaderLanguage::SPIRV; diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index bd89267af6..03cd2f8605 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -57,7 +57,9 @@ static MaterialParser* createParser(Backend backend, ShaderLanguage language, } ASSERT_PRECONDITION(materialResult != MaterialParser::ParseResult::ERROR_MISSING_BACKEND, - "the material was not built for the %s backend\n", backendToString(backend)); + "the material was not built for the %s backend and %s shader language\n", + backendToString(backend), + shaderLanguageToString(language)); ASSERT_PRECONDITION(materialResult == MaterialParser::ParseResult::SUCCESS, "could not parse the material package"); @@ -184,7 +186,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) success = parser->getUIB(&mUniformInterfaceBlock); assert_invariant(success); - if (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0) { + if (UTILS_UNLIKELY(engine.getShaderLanguage() == ShaderLanguage::ESSL1)) { success = parser->getBindingUniformInfo(&mBindingUniformInfo); assert_invariant(success); @@ -258,7 +260,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_POWER_VR_SHADER_WORKAROUNDS, (bool)powerVrShaderWorkarounds }); - if (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0) { + if (UTILS_UNLIKELY(engine.getShaderLanguage() == ShaderLanguage::ESSL1)) { // The actual value of this spec-constant is set in the OpenGLDriver backend. mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_SRGB_SWAPCHAIN_EMULATION, @@ -640,7 +642,6 @@ Program FMaterial::getProgramWithVariants( FEngine const& engine = mEngine; const ShaderModel sm = engine.getShaderModel(); const bool isNoop = engine.getBackend() == Backend::NOOP; - const FeatureLevel engineFeatureLevel = engine.getActiveFeatureLevel(); /* * Vertex shader */ @@ -694,7 +695,7 @@ Program FMaterial::getProgramWithVariants( } } - if (engineFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) { + if (UTILS_UNLIKELY(mEngine.getShaderLanguage() == ShaderLanguage::ESSL1)) { assert_invariant(!mBindingUniformInfo.empty()); for (auto const& [index, uniforms] : mBindingUniformInfo) { program.uniforms(uint32_t(index), uniforms); diff --git a/filament/src/details/Skybox.cpp b/filament/src/details/Skybox.cpp index 969eccdb45..7c0dac1512 100644 --- a/filament/src/details/Skybox.cpp +++ b/filament/src/details/Skybox.cpp @@ -118,12 +118,9 @@ FSkybox::FSkybox(FEngine& engine, const Builder& builder) noexcept FMaterial const* FSkybox::createMaterial(FEngine& engine) { Material::Builder builder; -#ifdef FILAMENT_TARGET_MOBILE if (UTILS_UNLIKELY(engine.getActiveFeatureLevel() == Engine::FeatureLevel::FEATURE_LEVEL_0)) { builder.package(MATERIALS_SKYBOX0_DATA, MATERIALS_SKYBOX0_SIZE); - } else -#endif - { + } else { builder.package(MATERIALS_SKYBOX_DATA, MATERIALS_SKYBOX_SIZE); } auto material = builder.build(engine); diff --git a/filament/src/details/View.cpp b/filament/src/details/View.cpp index b02d50c5c7..6308e1ef17 100644 --- a/filament/src/details/View.cpp +++ b/filament/src/details/View.cpp @@ -662,7 +662,6 @@ void FView::bindPerViewUniformsAndSamplers(FEngine::DriverApi& driver) const noe mPerViewUniforms.bind(driver); if (UTILS_UNLIKELY(driver.getFeatureLevel() == backend::FeatureLevel::FEATURE_LEVEL_0)) { - // FIXME: should be okay to use driver (instead of engine) for FEATURE_LEVEL_0 checks return; } diff --git a/libs/filamentapp/materials/transparentColor.mat b/libs/filamentapp/materials/transparentColor.mat index c43f9410e7..7ad51944dd 100644 --- a/libs/filamentapp/materials/transparentColor.mat +++ b/libs/filamentapp/materials/transparentColor.mat @@ -9,7 +9,8 @@ material { blending : transparent, culling : none, depthCulling : false, - shadingModel : unlit + shadingModel : unlit, + featureLevel : 0 } fragment { diff --git a/libs/filamentapp/src/FilamentApp.cpp b/libs/filamentapp/src/FilamentApp.cpp index 51ba611d14..7970c61f3a 100644 --- a/libs/filamentapp/src/FilamentApp.cpp +++ b/libs/filamentapp/src/FilamentApp.cpp @@ -603,10 +603,14 @@ FilamentApp::Window::Window(FilamentApp* filamentApp, return Engine::Builder() .backend(backend) .platform(mFilamentApp->mVulkanPlatform) + .featureLevel(config.featureLevel) .build(); #endif } - return Engine::Builder().backend(backend).build(); + return Engine::Builder() + .backend(backend) + .featureLevel(config.featureLevel) + .build(); }; if (config.headless) { @@ -647,10 +651,8 @@ FilamentApp::Window::Window(FilamentApp* filamentApp, #endif - // Select the feature level to use - config.featureLevel = std::min(config.featureLevel, - mFilamentApp->mEngine->getSupportedFeatureLevel()); - mFilamentApp->mEngine->setActiveFeatureLevel(config.featureLevel); + // Write back the active feature level. + config.featureLevel = mFilamentApp->mEngine->getActiveFeatureLevel(); mSwapChain = mFilamentApp->mEngine->createSwapChain( nativeSwapChain, filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER); diff --git a/samples/hellotriangle.cpp b/samples/hellotriangle.cpp index c6469fc1b3..4477429dd6 100644 --- a/samples/hellotriangle.cpp +++ b/samples/hellotriangle.cpp @@ -122,6 +122,7 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) { int main(int argc, char** argv) { App app{}; app.config.title = "hellotriangle"; + app.config.featureLevel = backend::FeatureLevel::FEATURE_LEVEL_0; handleCommandLineArguments(argc, argv, &app); auto setup = [&app](Engine* engine, View* view, Scene* scene) { diff --git a/samples/materials/bakedColor.mat b/samples/materials/bakedColor.mat index ee10a3035e..c78cef16ac 100644 --- a/samples/materials/bakedColor.mat +++ b/samples/materials/bakedColor.mat @@ -4,7 +4,8 @@ material { color ], shadingModel : unlit, - culling : none + culling : none, + featureLevel : 0 } fragment {