From 35fa79ec235ebf5ee4b930c0d44a372719bcedfa Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Sun, 21 Apr 2024 21:59:22 -0400 Subject: [PATCH] Support backends with multiple shader languages and precompiled Metal libraries (#7769) --- filament/backend/include/backend/Program.h | 6 ++ filament/backend/src/Program.cpp | 7 +- .../backend/src/metal/MetalShaderCompiler.mm | 53 +++++++++----- filament/backend/test/ShaderGenerator.cpp | 15 ++++ filament/backend/test/ShaderGenerator.h | 1 + filament/backend/test/test_ComputeBasic.cpp | 2 + filament/src/MaterialParser.cpp | 70 ++++++++++++------- filament/src/MaterialParser.h | 16 +++-- filament/src/details/Engine.h | 19 ++--- filament/src/details/Material.cpp | 47 ++++++++----- .../test/filament_test_material_parser.cpp | 2 +- 11 files changed, 159 insertions(+), 79 deletions(-) diff --git a/filament/backend/include/backend/Program.h b/filament/backend/include/backend/Program.h index fe1c4a9b6e..b5c1dd9bab 100644 --- a/filament/backend/include/backend/Program.h +++ b/filament/backend/include/backend/Program.h @@ -84,6 +84,9 @@ public: // null terminating character. Program& shader(ShaderStage shader, void const* data, size_t size); + // sets the language of the shader sources provided with shader() (defaults to ESSL3) + Program& shaderLanguage(ShaderLanguage shaderLanguage); + // Note: This is only needed for GLES3.0 backends, because the layout(binding=) syntax is // not permitted in glsl. The backend needs a way to associate a uniform block // to a binding point. @@ -136,6 +139,8 @@ public: utils::CString const& getName() const noexcept { return mName; } utils::CString& getName() noexcept { return mName; } + auto const& getShaderLanguage() const { return mShaderLanguage; } + utils::FixedCapacityVector const& getSpecializationConstants() const noexcept { return mSpecializationConstants; } @@ -155,6 +160,7 @@ private: UniformBlockInfo mUniformBlocks = {}; SamplerGroupInfo mSamplerGroups = {}; ShaderSource mShadersSource; + ShaderLanguage mShaderLanguage = ShaderLanguage::ESSL3; utils::CString mName; uint64_t mCacheId{}; utils::Invocable mLogger; diff --git a/filament/backend/src/Program.cpp b/filament/backend/src/Program.cpp index dc92e8c2a2..39a941485e 100644 --- a/filament/backend/src/Program.cpp +++ b/filament/backend/src/Program.cpp @@ -21,7 +21,7 @@ namespace filament::backend { using namespace utils; // We want these in the .cpp file, so they're not inlined (not worth it) -Program::Program() noexcept { // NOLINT(modernize-use-equals-default) +Program::Program() noexcept { // NOLINT(modernize-use-equals-default) } Program::Program(Program&& rhs) noexcept = default; @@ -47,6 +47,11 @@ Program& Program::shader(ShaderStage shader, void const* data, size_t size) { return *this; } +Program& Program::shaderLanguage(ShaderLanguage shaderLanguage) { + mShaderLanguage = shaderLanguage; + return *this; +} + Program& Program::uniformBlockBindings( FixedCapacityVector> const& uniformBlockBindings) noexcept { for (auto const& item : uniformBlockBindings) { diff --git a/filament/backend/src/metal/MetalShaderCompiler.mm b/filament/backend/src/metal/MetalShaderCompiler.mm index 7741ae5448..1aa02f9c87 100644 --- a/filament/backend/src/metal/MetalShaderCompiler.mm +++ b/filament/backend/src/metal/MetalShaderCompiler.mm @@ -106,26 +106,41 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept { continue; } - assert_invariant(source[source.size() - 1] == '\0'); - - // the shader string is null terminated and the length includes the null character - NSString* objcSource = [[NSString alloc] initWithBytes:source.data() - length:source.size() - 1 - encoding:NSUTF8StringEncoding]; - - // By default, Metal uses the most recent language version. - MTLCompileOptions* options = [MTLCompileOptions new]; - - // Disable Fast Math optimizations. - // This ensures that operations adhere to IEEE standards for floating-point arithmetic, - // which is crucial for half precision floats in scenarios where fast math optimizations - // lead to inaccuracies, such as in handling special values like NaN or Infinity. - options.fastMathEnabled = NO; - NSError* error = nil; - id library = [device newLibraryWithSource:objcSource - options:options - error:&error]; + id library = nil; + switch (program.getShaderLanguage()) { + case ShaderLanguage::MSL: { + // By default, Metal uses the most recent language version. + MTLCompileOptions* options = [MTLCompileOptions new]; + + // Disable Fast Math optimizations. + // This ensures that operations adhere to IEEE standards for floating-point + // arithmetic, which is crucial for half precision floats in scenarios where fast + // math optimizations lead to inaccuracies, such as in handling special values like + // NaN or Infinity. + options.fastMathEnabled = NO; + + assert_invariant(source[source.size() - 1] == '\0'); + // the shader string is null terminated and the length includes the null character + NSString* objcSource = [[NSString alloc] initWithBytes:source.data() + length:source.size() - 1 + encoding:NSUTF8StringEncoding]; + library = [device newLibraryWithSource:objcSource options:options error:&error]; + break; + } + case ShaderLanguage::METAL_LIBRARY: { + dispatch_data_t data = dispatch_data_create(source.data(), source.size(), + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + DISPATCH_DATA_DESTRUCTOR_DEFAULT); + library = [device newLibraryWithData:data error:&error]; + break; + } + case ShaderLanguage::ESSL1: + case ShaderLanguage::ESSL3: + case ShaderLanguage::SPIRV: + break; + } + if (library == nil) { NSString* errorMessage = @"unknown error"; if (error) { diff --git a/filament/backend/test/ShaderGenerator.cpp b/filament/backend/test/ShaderGenerator.cpp index 8e329489dd..7d0ad716e1 100644 --- a/filament/backend/test/ShaderGenerator.cpp +++ b/filament/backend/test/ShaderGenerator.cpp @@ -79,6 +79,20 @@ ShaderGenerator::ShaderGenerator(std::string vertex, std::string fragment, mVertexBlob(transpileShader(ShaderStage::VERTEX, std::move(vertex), backend, isMobile, sib)), mFragmentBlob(transpileShader(ShaderStage::FRAGMENT, std::move(fragment), backend, isMobile, sib)) { + switch (backend) { + case Backend::OPENGL: + mShaderLanguage = filament::backend::ShaderLanguage::ESSL3; + break; + case Backend::VULKAN: + mShaderLanguage = filament::backend::ShaderLanguage::SPIRV; + break; + case Backend::METAL: + mShaderLanguage = filament::backend::ShaderLanguage::MSL; + break; + case Backend::NOOP: + mShaderLanguage = filament::backend::ShaderLanguage::ESSL3; + break; + } } ShaderGenerator::Blob ShaderGenerator::transpileShader( @@ -160,6 +174,7 @@ ShaderGenerator::Blob ShaderGenerator::transpileShader( Program ShaderGenerator::getProgram(filament::backend::DriverApi&) noexcept { Program program; + program.shaderLanguage(mShaderLanguage); program.shader(ShaderStage::VERTEX, mVertexBlob.data(), mVertexBlob.size()); program.shader(ShaderStage::FRAGMENT, mFragmentBlob.data(), mFragmentBlob.size()); return program; diff --git a/filament/backend/test/ShaderGenerator.h b/filament/backend/test/ShaderGenerator.h index 1962998659..39a87a7f20 100644 --- a/filament/backend/test/ShaderGenerator.h +++ b/filament/backend/test/ShaderGenerator.h @@ -60,6 +60,7 @@ private: Blob mFragmentBlob; std::string mCompiledVertexShader; std::string mCompiledFragmentShader; + filament::backend::ShaderLanguage mShaderLanguage; }; diff --git a/filament/backend/test/test_ComputeBasic.cpp b/filament/backend/test/test_ComputeBasic.cpp index c0475908df..7062259559 100644 --- a/filament/backend/test/test_ComputeBasic.cpp +++ b/filament/backend/test/test_ComputeBasic.cpp @@ -58,6 +58,7 @@ kernel void main0() {} } Program program; + program.shaderLanguage(ShaderLanguage::ESSL3); program.shader(ShaderStage::COMPUTE, shader.data(), shader.size() + 1); Handle ph = driver.createProgram(std::move(program)); @@ -144,6 +145,7 @@ kernel void main0(device Output_data& output_data [[buffer(0)]], driver.updateBufferObject(input_data, { data.data(), size }, 0); Program program; + program.shaderLanguage(ShaderLanguage::ESSL3); program.shader(ShaderStage::COMPUTE, shader.data(), shader.size() + 1); Handle ph = driver.createProgram(std::move(program)); diff --git a/filament/src/MaterialParser.cpp b/filament/src/MaterialParser.cpp index d0992ec4eb..b5aeda47cd 100644 --- a/filament/src/MaterialParser.cpp +++ b/filament/src/MaterialParser.cpp @@ -34,6 +34,7 @@ #include #include +#include using namespace utils; using namespace filament::backend; @@ -42,36 +43,31 @@ using namespace filamat; namespace filament { -// ------------------------------------------------------------------------------------------------ - -MaterialParser::MaterialParserDetails::MaterialParserDetails(ShaderLanguage language, const void* data, size_t size) - : mManagedBuffer(data, size), - mChunkContainer(mManagedBuffer.data(), mManagedBuffer.size()), - mMaterialChunk(mChunkContainer) { +constexpr std::pair shaderLanguageToTags(ShaderLanguage language) { switch (language) { case ShaderLanguage::ESSL3: - mMaterialTag = ChunkType::MaterialGlsl; - mDictionaryTag = ChunkType::DictionaryText; - break; + return { ChunkType::MaterialGlsl, ChunkType::DictionaryText }; case ShaderLanguage::ESSL1: - mMaterialTag = ChunkType::MaterialEssl1; - mDictionaryTag = ChunkType::DictionaryText; - break; + return { ChunkType::MaterialEssl1, ChunkType::DictionaryText }; case ShaderLanguage::MSL: - mMaterialTag = ChunkType::MaterialMetal; - mDictionaryTag = ChunkType::DictionaryText; - break; + return { ChunkType::MaterialMetal, ChunkType::DictionaryText }; case ShaderLanguage::SPIRV: - mMaterialTag = ChunkType::MaterialSpirv; - mDictionaryTag = ChunkType::DictionarySpirv; - break; + return { ChunkType::MaterialSpirv, ChunkType::DictionarySpirv }; case ShaderLanguage::METAL_LIBRARY: - mMaterialTag = ChunkType::MaterialMetalLibrary; - mDictionaryTag = ChunkType::DictionaryMetalLibrary; - break; + return { ChunkType::MaterialMetalLibrary, ChunkType::DictionaryMetalLibrary }; } } +// ------------------------------------------------------------------------------------------------ + +MaterialParser::MaterialParserDetails::MaterialParserDetails( + const utils::FixedCapacityVector& preferredLanguages, const void* data, + size_t size) + : mManagedBuffer(data, size), + mChunkContainer(mManagedBuffer.data(), mManagedBuffer.size()), + mPreferredLanguages(preferredLanguages), + mMaterialChunk(mChunkContainer) {} + template UTILS_NOINLINE bool MaterialParser::MaterialParserDetails::getFromSimpleChunk( @@ -87,9 +83,9 @@ bool MaterialParser::MaterialParserDetails::getFromSimpleChunk( // ------------------------------------------------------------------------------------------------ -MaterialParser::MaterialParser(ShaderLanguage language, const void* data, size_t size) - : mImpl(language, data, size) { -} +MaterialParser::MaterialParser(utils::FixedCapacityVector preferredLanguages, + const void* data, size_t size) + : mImpl(preferredLanguages, data, size) {} ChunkContainer& MaterialParser::getChunkContainer() noexcept { return mImpl.mChunkContainer; @@ -104,20 +100,40 @@ MaterialParser::ParseResult MaterialParser::parse() noexcept { if (UTILS_UNLIKELY(!cc.parse())) { return ParseResult::ERROR_OTHER; } - const ChunkType matTag = mImpl.mMaterialTag; - const ChunkType dictTag = mImpl.mDictionaryTag; - if (UTILS_UNLIKELY(!cc.hasChunk(matTag) || !cc.hasChunk(dictTag))) { + + using MaybeShaderLanguageAndChunks = + std::optional>; + auto chooseLanguage = [this, &cc]() -> MaybeShaderLanguageAndChunks { + for (auto language : mImpl.mPreferredLanguages) { + const auto [matTag, dictTag] = shaderLanguageToTags(language); + if (cc.hasChunk(matTag) && cc.hasChunk(dictTag)) { + return std::make_tuple(language, matTag, dictTag); + } + } + return {}; + }; + const auto result = chooseLanguage(); + + if (!result.has_value()) { return ParseResult::ERROR_MISSING_BACKEND; } + + const auto [chosenLanguage, matTag, dictTag] = result.value(); if (UTILS_UNLIKELY(!DictionaryReader::unflatten(cc, dictTag, mImpl.mBlobDictionary))) { return ParseResult::ERROR_OTHER; } if (UTILS_UNLIKELY(!mImpl.mMaterialChunk.initialize(matTag))) { return ParseResult::ERROR_OTHER; } + + mImpl.mChosenLanguage = chosenLanguage; return ParseResult::SUCCESS; } +backend::ShaderLanguage MaterialParser::getShaderLanguage() const noexcept { + return mImpl.mChosenLanguage; +} + // Accessors bool MaterialParser::getMaterialVersion(uint32_t* value) const noexcept { return mImpl.getFromSimpleChunk(ChunkType::MaterialVersion, value); diff --git a/filament/src/MaterialParser.h b/filament/src/MaterialParser.h index 638e93d566..2ec7f7c1cc 100644 --- a/filament/src/MaterialParser.h +++ b/filament/src/MaterialParser.h @@ -29,10 +29,12 @@ #include #include -#include #include +#include +#include #include +#include namespace filaflat { class ChunkContainer; @@ -48,7 +50,8 @@ struct MaterialConstant; class MaterialParser { public: - MaterialParser(backend::ShaderLanguage language, const void* data, size_t size); + MaterialParser(utils::FixedCapacityVector preferredLanguages, + const void* data, size_t size); MaterialParser(MaterialParser const& rhs) noexcept = delete; MaterialParser& operator=(MaterialParser const& rhs) noexcept = delete; @@ -60,6 +63,7 @@ public: }; ParseResult parse() noexcept; + backend::ShaderLanguage getShaderLanguage() const noexcept; // Accessors bool getMaterialVersion(uint32_t* value) const noexcept; @@ -130,7 +134,9 @@ public: private: struct MaterialParserDetails { - MaterialParserDetails(backend::ShaderLanguage language, const void* data, size_t size); + MaterialParserDetails( + const utils::FixedCapacityVector& preferredLanguages, + const void* data, size_t size); template bool getFromSimpleChunk(filamat::ChunkType type, T* value) const noexcept; @@ -157,12 +163,12 @@ private: ManagedBuffer mManagedBuffer; filaflat::ChunkContainer mChunkContainer; + utils::FixedCapacityVector mPreferredLanguages; + backend::ShaderLanguage mChosenLanguage; // Keep MaterialChunk alive between calls to getShader to avoid reload the shader index. filaflat::MaterialChunk mMaterialChunk; filaflat::BlobDictionary mBlobDictionary; - filamat::ChunkType mMaterialTag = filamat::ChunkType::Unknown; - filamat::ChunkType mDictionaryTag = filamat::ChunkType::Unknown; }; filaflat::ChunkContainer& getChunkContainer() noexcept; diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index b467013b99..3d02cbbd5c 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -59,10 +59,11 @@ #include #include -#include #include -#include #include +#include +#include +#include #include #include @@ -231,19 +232,21 @@ public: return mPlatform; } - backend::ShaderLanguage getShaderLanguage() const noexcept { + // Return a vector of shader languages, in order of preference. + utils::FixedCapacityVector getShaderLanguage() const noexcept { switch (mBackend) { case Backend::DEFAULT: case Backend::NOOP: default: - return backend::ShaderLanguage::ESSL3; + return { backend::ShaderLanguage::ESSL3 }; case Backend::OPENGL: - return getDriver().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0 - ? backend::ShaderLanguage::ESSL1 : backend::ShaderLanguage::ESSL3; + return { getDriver().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0 + ? backend::ShaderLanguage::ESSL1 + : backend::ShaderLanguage::ESSL3 }; case Backend::VULKAN: - return backend::ShaderLanguage::SPIRV; + return { backend::ShaderLanguage::SPIRV }; case Backend::METAL: - return backend::ShaderLanguage::MSL; + return { backend::ShaderLanguage::METAL_LIBRARY, backend::ShaderLanguage::MSL }; } } diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index 319069d304..b5597a06e7 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -42,16 +42,16 @@ #include #include +#include +#include +#include +#include +#include +#include #include #include #include -#include -#include -#include -#include #include -#include -#include #include #include @@ -60,6 +60,7 @@ #include #include #include +#include #include #include #include @@ -74,22 +75,32 @@ using namespace backend; using namespace filaflat; using namespace utils; -static std::unique_ptr createParser( - Backend backend, ShaderLanguage language, const void* data, size_t size) { +static std::unique_ptr createParser(Backend backend, + utils::FixedCapacityVector languages, const void* data, size_t size) { // unique_ptr so we don't leak MaterialParser on failures below - auto materialParser = std::make_unique(language, data, size); + auto materialParser = std::make_unique(languages, data, size); MaterialParser::ParseResult const materialResult = materialParser->parse(); + if (UTILS_UNLIKELY(materialResult == MaterialParser::ParseResult::ERROR_MISSING_BACKEND)) { + std::stringstream languageNames; + for (auto it = languages.begin(); it != languages.end(); ++it) { + languageNames << shaderLanguageToString(*it); + if (std::next(it) != languages.end()) { + languageNames << ", "; + } + } + + ASSERT_PRECONDITION(materialResult != MaterialParser::ParseResult::ERROR_MISSING_BACKEND, + "the material was not built for any of the %s backend's supported shader " + "languages (%s)\n", + backendToString(backend), languageNames.str().c_str()); + } + if (backend == Backend::NOOP) { return materialParser; } - ASSERT_PRECONDITION(materialResult != MaterialParser::ParseResult::ERROR_MISSING_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"); @@ -217,7 +228,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder, success = parser->getUIB(&mUniformInterfaceBlock); assert_invariant(success); - if (UTILS_UNLIKELY(engine.getShaderLanguage() == ShaderLanguage::ESSL1)) { + if (UTILS_UNLIKELY(parser->getShaderLanguage() == ShaderLanguage::ESSL1)) { success = parser->getBindingUniformInfo(&mBindingUniformInfo); assert_invariant(success); @@ -530,6 +541,7 @@ Program FMaterial::getProgramWithVariants( Program program; program.shader(ShaderStage::VERTEX, vsBuilder.data(), vsBuilder.size()) .shader(ShaderStage::FRAGMENT, fsBuilder.data(), fsBuilder.size()) + .shaderLanguage(mMaterialParser->getShaderLanguage()) .uniformBlockBindings(mUniformBlockBindings) .diagnostics(mName, [this, variant](io::ostream& out) -> io::ostream& { @@ -551,8 +563,7 @@ Program FMaterial::getProgramWithVariants( samplers.data(), info.count); } } - - if (UTILS_UNLIKELY(mEngine.getShaderLanguage() == ShaderLanguage::ESSL1)) { + if (UTILS_UNLIKELY(mMaterialParser->getShaderLanguage() == ShaderLanguage::ESSL1)) { assert_invariant(!mBindingUniformInfo.empty()); for (auto const& [index, uniforms] : mBindingUniformInfo) { program.uniforms(uint32_t(index), uniforms); @@ -861,7 +872,7 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Material::Builde mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT, (int)engine.getConfig().stereoscopicEyeCount }); - if (UTILS_UNLIKELY(engine.getShaderLanguage() == ShaderLanguage::ESSL1)) { + if (UTILS_UNLIKELY(parser->getShaderLanguage() == ShaderLanguage::ESSL1)) { // The actual value of this spec-constant is set in the OpenGLDriver backend. mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_SRGB_SWAPCHAIN_EMULATION, diff --git a/filament/test/filament_test_material_parser.cpp b/filament/test/filament_test_material_parser.cpp index 100ac2f1cf..853b540b30 100644 --- a/filament/test/filament_test_material_parser.cpp +++ b/filament/test/filament_test_material_parser.cpp @@ -31,7 +31,7 @@ using namespace filament; // This will re-compile the test material with the current version of matc. // To verify, rebuild and re-run test_material_parser (this test suite). TEST(MaterialParser, Parse) { - MaterialParser parser(backend::ShaderLanguage::ESSL3, + MaterialParser parser({ backend::ShaderLanguage::ESSL3 }, FILAMENT_TEST_RESOURCES_TEST_MATERIAL_DATA, FILAMENT_TEST_RESOURCES_TEST_MATERIAL_SIZE); MaterialParser::ParseResult materialOk = parser.parse();