From 7acee6c97af7d6074cc0c0dc1e0f08d6df3b46bc Mon Sep 17 00:00:00 2001 From: Eliza Velasquez Date: Wed, 10 Sep 2025 16:53:06 -0700 Subject: [PATCH 1/7] materials: introduce MaterialCache Presently, Filament Materials are instantiated by first parsing a bunch of read-only data from a material file, then applying a bunch of options from its Builder before settling on a final, immutable Material object. If two different Material instances need to be parameterized differently, e.g. setting their spec constants independently, each has to do all of these steps independently for each variation. This change introduces two new concepts: MaterialDefinition, representing the deserialized, read-only state of a material file, and MaterialCache, a reference-counted system responsible for managing the lifetimes of MaterialDefinitions. Now, each Material asks the cache if a MaterialDefinition exists for the particular UUID of the data it's trying to read; if not, MaterialCache creates a new entry transparently. If a hundred different Materials all try to load the same material data, only one MaterialDefinition (and its associated GPU resources) will be created. This first PR is the least possible invasive implementation of this feature. There are a lot of room for improvements (and more planned). For example, each Material still manages its own compiled shader program cache, but we can easily move this to the MaterialCache in future PRs, further enabling the planned mutable spec constants feature. Additionally, there's room here to add a Material::toBuilder() method, which could take an extant material and create a Builder object from it already parameterized with all of the same options, a la the prototype design pattern. --- filament/CMakeLists.txt | 5 + filament/src/MaterialCache.cpp | 59 ++ filament/src/MaterialCache.h | 60 ++ filament/src/MaterialDefinition.cpp | 402 +++++++++++++ filament/src/MaterialDefinition.h | 127 +++++ filament/src/MaterialParser.cpp | 16 +- filament/src/MaterialParser.h | 4 +- filament/src/RefCountedMap.h | 191 +++++++ filament/src/details/Engine.cpp | 4 +- filament/src/details/Engine.h | 9 +- filament/src/details/Material.cpp | 532 +++--------------- filament/src/details/Material.h | 162 ++---- .../filaflat/include/filaflat/MaterialChunk.h | 6 +- libs/filaflat/src/MaterialChunk.cpp | 7 +- 14 files changed, 1019 insertions(+), 565 deletions(-) create mode 100644 filament/src/MaterialCache.cpp create mode 100644 filament/src/MaterialCache.h create mode 100644 filament/src/MaterialDefinition.cpp create mode 100644 filament/src/MaterialDefinition.h create mode 100644 filament/src/RefCountedMap.h diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index 1628f9d06e99..be46022f7aaa 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -84,6 +84,8 @@ set(SRCS src/InstanceBuffer.cpp src/LightManager.cpp src/Material.cpp + src/MaterialCache.cpp + src/MaterialDefinition.cpp src/MaterialInstance.cpp src/MaterialInstanceManager.cpp src/MaterialParser.cpp @@ -175,10 +177,13 @@ set(PRIVATE_HDRS src/HwRenderPrimitiveFactory.h src/HwVertexBufferInfoFactory.h src/Intersections.h + src/MaterialCache.h + src/MaterialDefinition.h src/MaterialParser.h src/MaterialInstanceManager.h src/PIDController.h src/PostProcessManager.h + src/RefCountedMap.h src/RenderPass.h src/RenderPrimitive.h src/RendererUtils.h diff --git a/filament/src/MaterialCache.cpp b/filament/src/MaterialCache.cpp new file mode 100644 index 000000000000..7a931276dbea --- /dev/null +++ b/filament/src/MaterialCache.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MaterialCache.h" +#include "MaterialParser.h" + +#include + +#include
+#include
+ +#include + +namespace filament { + +MaterialCache::~MaterialCache() { + if (!mInner.empty()) { + LOG(WARNING) << "MaterialCache was destroyed but wasn't empty"; + } +} + +MaterialDefinition* UTILS_NULLABLE MaterialCache::acquire(FEngine& engine, + const void* UTILS_NONNULL data, size_t size) noexcept { + std::unique_ptr parser = MaterialDefinition::createParser( + engine.getBackend(), engine.getShaderLanguage(), data, size); + if (!parser) { + return nullptr; + } + + uint64_t uuid; + if (UTILS_UNLIKELY(!parser->getCacheId(&uuid))) { + return nullptr; + } + + return mInner.acquire(uuid, [&engine, parser = std::move(parser)]() mutable { + return MaterialDefinition::create(engine, std::move(parser)); + }); +} + +void MaterialCache::release(FEngine& engine, uint64_t uuid) noexcept { + mInner.release(uuid, [&engine](MaterialDefinition& definition) { + definition.terminate(engine); + }); +} + +} // namespace filament diff --git a/filament/src/MaterialCache.h b/filament/src/MaterialCache.h new file mode 100644 index 000000000000..e2577ac02ca7 --- /dev/null +++ b/filament/src/MaterialCache.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TNT_FILAMENT_MATERIALCACHE_H +#define TNT_FILAMENT_MATERIALCACHE_H + +#include "RefCountedMap.h" +#include "MaterialDefinition.h" + +#include + +#include +#include +#include +#include + +#include + +namespace filament { + +class Material; + +/** A cache of all materials and shader programs compiled by Filament. */ +class MaterialCache { + // Program caches are keyed by the material UUID. + // + // We use unique_ptr here because we need these pointers to be stable. + // TODO: investigate using a custom allocator here? + using Inner = RefCountedMap>; + +public: + ~MaterialCache(); + + /** Acquire or create a new entry in the cache for the given material data. */ + MaterialDefinition* UTILS_NULLABLE acquire(FEngine& engine, const void* UTILS_NONNULL data, + size_t size) noexcept; + + /** Release an entry in the cache, potentially freeing its GPU resources. */ + void release(FEngine& engine, uint64_t uuid) noexcept; + +private: + Inner mInner; +}; + +} // namespace filament + + +#endif // TNT_FILAMENT_MATERIALCACHE_H diff --git a/filament/src/MaterialDefinition.cpp b/filament/src/MaterialDefinition.cpp new file mode 100644 index 000000000000..fba8b906b51a --- /dev/null +++ b/filament/src/MaterialDefinition.cpp @@ -0,0 +1,402 @@ +#include "MaterialDefinition.h" + +#include "Froxelizer.h" +#include "MaterialParser.h" + +#include + +#include
+ +#include +#include + +namespace filament { + +using namespace backend; +using namespace utils; + +namespace { + +static const char* toString(ShaderModel model) { + switch (model) { + case ShaderModel::MOBILE: + return "mobile"; + case ShaderModel::DESKTOP: + return "desktop"; + } +} + +} // namespace + +std::unique_ptr MaterialDefinition::createParser(Backend const backend, + FixedCapacityVector languages, const void* data, size_t size) { + // unique_ptr so we don't leak MaterialParser on failures below + auto materialParser = std::make_unique(languages, data, size); + + MaterialParser::ParseResult const materialResult = materialParser->parse(); + + if (UTILS_UNLIKELY(materialResult == MaterialParser::ParseResult::ERROR_MISSING_BACKEND)) { + CString languageNames; + for (auto it = languages.begin(); it != languages.end(); ++it) { + languageNames.append(CString{shaderLanguageToString(*it)}); + if (std::next(it) != languages.end()) { + languageNames.append(", "); + } + } + + FILAMENT_CHECK_POSTCONDITION( + materialResult != MaterialParser::ParseResult::ERROR_MISSING_BACKEND) + << "the material was not built for any of the " << to_string(backend) + << " backend's supported shader languages (" << languageNames.c_str() << ")\n"; + } + + if (backend == Backend::NOOP) { + return materialParser; + } + + FILAMENT_CHECK_POSTCONDITION(materialResult == MaterialParser::ParseResult::SUCCESS) + << "could not parse the material package"; + + uint32_t version = 0; + materialParser->getMaterialVersion(&version); + FILAMENT_CHECK_POSTCONDITION(version == MATERIAL_VERSION) + << "Material version mismatch. Expected " << MATERIAL_VERSION << " but received " + << version << "."; + + assert_invariant(backend != Backend::DEFAULT && "Default backend has not been resolved."); + + return materialParser; +} + +std::unique_ptr MaterialDefinition::create(FEngine& engine, + std::unique_ptr parser) { + // Try checking CRC32 value for the package and skip if it's unavailable. + if (downcast(engine).features.material.check_crc32_after_loading) { + uint32_t parsedCrc32 = 0; + parser->getMaterialCrc32(&parsedCrc32); + + uint32_t expectedCrc32 = parser->computeCrc32(); + + if (parsedCrc32 != expectedCrc32) { + CString name; + parser->getName(&name); + LOG(ERROR) << "The material '" << name.c_str_safe() + << "' is corrupted: crc32_expected=" << expectedCrc32 + << ", crc32_parsed=" << parsedCrc32; + return nullptr; + } + } + + uint32_t v = 0; + parser->getShaderModels(&v); + bitset32 shaderModels; + shaderModels.setValue(v); + + ShaderModel const shaderModel = downcast(engine).getShaderModel(); + if (!shaderModels.test(static_cast(shaderModel))) { + CString name; + parser->getName(&name); + char shaderModelsString[16]; + snprintf(shaderModelsString, sizeof(shaderModelsString), "%#x", shaderModels.getValue()); + LOG(ERROR) << "The material '" << name.c_str_safe() << "' was not built for " + << toString(shaderModel) << "."; + LOG(ERROR) << "Compiled material contains shader models " << shaderModelsString << "."; + return nullptr; + } + + // Print a warning if the material's stereo type doesn't align with the engine's + // setting. + MaterialDomain materialDomain; + UserVariantFilterMask variantFilterMask; + parser->getMaterialDomain(&materialDomain); + parser->getMaterialVariantFilterMask(&variantFilterMask); + bool const hasStereoVariants = + !(variantFilterMask & UserVariantFilterMask(UserVariantFilterBit::STE)); + if (materialDomain == MaterialDomain::SURFACE && hasStereoVariants) { + StereoscopicType const engineStereoscopicType = engine.getConfig().stereoscopicType; + // Default materials are always compiled with either 'instanced' or 'multiview'. + // So, we only verify compatibility if the engine is set up for stereo. + if (engineStereoscopicType != StereoscopicType::NONE) { + StereoscopicType materialStereoscopicType = StereoscopicType::NONE; + parser->getStereoscopicType(&materialStereoscopicType); + if (materialStereoscopicType != engineStereoscopicType) { + CString name; + parser->getName(&name); + LOG(WARNING) << "The stereoscopic type in the compiled material '" + << name.c_str_safe() << "' is " << (int) materialStereoscopicType + << ", which is not compatible with the engine's setting " + << (int) engineStereoscopicType << "."; + } + } + } + + return std::make_unique(engine, std::move(parser)); +} + +void MaterialDefinition::terminate(FEngine& engine) { + DriverApi& driver = engine.getDriverApi(); + perViewDescriptorSetLayout.terminate(engine.getDescriptorSetLayoutFactory(), driver); + perViewDescriptorSetLayoutVsm.terminate(engine.getDescriptorSetLayoutFactory(), driver); + descriptorSetLayout.terminate(engine.getDescriptorSetLayoutFactory(), driver); +} + +MaterialDefinition::MaterialDefinition(FEngine& engine, + std::unique_ptr materialParser) + : materialParser(std::move(materialParser)) { + + processMain(); + processBlendingMode(); + processSpecializationConstants(); + processDescriptorSets(engine); +} + +void MaterialDefinition::processMain() { + UTILS_UNUSED_IN_RELEASE bool const nameOk = materialParser->getName(&name); + assert_invariant(nameOk); + + featureLevel = [this]() -> FeatureLevel { + // code written this way so the IDE will complain when/if we add a FeatureLevel + uint8_t level = 1; + materialParser->getFeatureLevel(&level); + assert_invariant(level <= 3); + FeatureLevel featureLevel = FeatureLevel::FEATURE_LEVEL_1; + switch (FeatureLevel(level)) { + case FeatureLevel::FEATURE_LEVEL_0: + case FeatureLevel::FEATURE_LEVEL_1: + case FeatureLevel::FEATURE_LEVEL_2: + case FeatureLevel::FEATURE_LEVEL_3: + featureLevel = FeatureLevel(level); + break; + } + return featureLevel; + }(); + + UTILS_UNUSED_IN_RELEASE bool success; + + success = materialParser->getCacheId(&cacheId); + assert_invariant(success); + + success = materialParser->getSIB(&samplerInterfaceBlock); + assert_invariant(success); + + success = materialParser->getUIB(&uniformInterfaceBlock); + assert_invariant(success); + + if (UTILS_UNLIKELY(materialParser->getShaderLanguage() == ShaderLanguage::ESSL1)) { + success = materialParser->getAttributeInfo(&attributeInfo); + assert_invariant(success); + + success = materialParser->getBindingUniformInfo(&bindingUniformInfo); + assert_invariant(success); + } + + // Older materials will not have a subpass chunk; this should not be an error. + if (!materialParser->getSubpasses(&subpassInfo)) { + subpassInfo.isValid = false; + } + + materialParser->getShading(&shading); + materialParser->getMaterialProperties(&materialProperties); + materialParser->getInterpolation(&interpolation); + materialParser->getVertexDomain(&vertexDomain); + materialParser->getMaterialDomain(&materialDomain); + materialParser->getMaterialVariantFilterMask(&variantFilterMask); + materialParser->getRequiredAttributes(&requiredAttributes); + materialParser->getRefractionMode(&refractionMode); + materialParser->getRefractionType(&refractionType); + materialParser->getReflectionMode(&reflectionMode); + materialParser->getTransparencyMode(&transparencyMode); + materialParser->getDoubleSided(&doubleSided); + materialParser->getCullingMode(&cullingMode); + + if (shading == Shading::UNLIT) { + materialParser->hasShadowMultiplier(&hasShadowMultiplier); + } + + isVariantLit = shading != Shading::UNLIT || hasShadowMultiplier; + + // color write + bool colorWrite = false; + materialParser->getColorWrite(&colorWrite); + rasterState.colorWrite = colorWrite; + + // depth test + bool depthTest = false; + materialParser->getDepthTest(&depthTest); + rasterState.depthFunc = depthTest ? RasterState::DepthFunc::GE : RasterState::DepthFunc::A; + + // if doubleSided() was called we override culling() + bool doubleSideSet = false; + materialParser->getDoubleSidedSet(&doubleSideSet); + if (doubleSideSet) { + doubleSidedCapability = true; + rasterState.culling = doubleSided ? CullingMode::NONE : cullingMode; + } else { + rasterState.culling = cullingMode; + } + + // specular anti-aliasing + materialParser->hasSpecularAntiAliasing(&specularAntiAliasing); + if (specularAntiAliasing) { + materialParser->getSpecularAntiAliasingVariance(&specularAntiAliasingVariance); + materialParser->getSpecularAntiAliasingThreshold(&specularAntiAliasingThreshold); + } + + materialParser->hasCustomDepthShader(&hasCustomDepthShader); + + bool const isLit = isVariantLit || hasShadowMultiplier; + bool const isSSR = reflectionMode == ReflectionMode::SCREEN_SPACE || + refractionMode == RefractionMode::SCREEN_SPACE; + bool const hasFog = !(variantFilterMask & UserVariantFilterMask(UserVariantFilterBit::FOG)); + + perViewLayoutIndex = ColorPassDescriptorSet::getIndex(isLit, isSSR, hasFog); +} + +void MaterialDefinition::processBlendingMode() { + materialParser->getBlendingMode(&blendingMode); + + if (blendingMode == BlendingMode::MASKED) { + materialParser->getMaskThreshold(&maskThreshold); + } + + if (blendingMode == BlendingMode::CUSTOM) { + materialParser->getCustomBlendFunction(&customBlendFunctions); + } + + // blending mode + switch (blendingMode) { + // Do not change the MASKED behavior without checking for regressions with + // AlphaBlendModeTest and TextureLinearInterpolationTest, with and without + // View::BlendMode::TRANSLUCENT. + case BlendingMode::MASKED: + case BlendingMode::OPAQUE: + rasterState.blendFunctionSrcRGB = BlendFunction::ONE; + rasterState.blendFunctionSrcAlpha = BlendFunction::ONE; + rasterState.blendFunctionDstRGB = BlendFunction::ZERO; + rasterState.blendFunctionDstAlpha = BlendFunction::ZERO; + rasterState.depthWrite = true; + break; + case BlendingMode::TRANSPARENT: + case BlendingMode::FADE: + rasterState.blendFunctionSrcRGB = BlendFunction::ONE; + rasterState.blendFunctionSrcAlpha = BlendFunction::ONE; + rasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA; + rasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_ALPHA; + rasterState.depthWrite = false; + break; + case BlendingMode::ADD: + rasterState.blendFunctionSrcRGB = BlendFunction::ONE; + rasterState.blendFunctionSrcAlpha = BlendFunction::ONE; + rasterState.blendFunctionDstRGB = BlendFunction::ONE; + rasterState.blendFunctionDstAlpha = BlendFunction::ONE; + rasterState.depthWrite = false; + break; + case BlendingMode::MULTIPLY: + rasterState.blendFunctionSrcRGB = BlendFunction::ZERO; + rasterState.blendFunctionSrcAlpha = BlendFunction::ZERO; + rasterState.blendFunctionDstRGB = BlendFunction::SRC_COLOR; + rasterState.blendFunctionDstAlpha = BlendFunction::SRC_COLOR; + rasterState.depthWrite = false; + break; + case BlendingMode::SCREEN: + rasterState.blendFunctionSrcRGB = BlendFunction::ONE; + rasterState.blendFunctionSrcAlpha = BlendFunction::ONE; + rasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_COLOR; + rasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_COLOR; + rasterState.depthWrite = false; + break; + case BlendingMode::CUSTOM: + rasterState.blendFunctionSrcRGB = customBlendFunctions[0]; + rasterState.blendFunctionSrcAlpha = customBlendFunctions[1]; + rasterState.blendFunctionDstRGB = customBlendFunctions[2]; + rasterState.blendFunctionDstAlpha = customBlendFunctions[3]; + rasterState.depthWrite = false; + } + + // depth write + bool depthWriteSet = false; + materialParser->getDepthWriteSet(&depthWriteSet); + if (depthWriteSet) { + bool depthWrite = false; + materialParser->getDepthWrite(&depthWrite); + rasterState.depthWrite = depthWrite; + } + + // alpha to coverage + bool alphaToCoverageSet = false; + materialParser->getAlphaToCoverageSet(&alphaToCoverageSet); + if (alphaToCoverageSet) { + bool alphaToCoverage = false; + materialParser->getAlphaToCoverage(&alphaToCoverage); + rasterState.alphaToCoverage = alphaToCoverage; + } else { + rasterState.alphaToCoverage = blendingMode == BlendingMode::MASKED; + } +} + +void MaterialDefinition::processSpecializationConstants() { + // Older materials won't have a constants chunk, but that's okay. + materialParser->getConstants(&materialConstants); + for (size_t i = 0, c = materialConstants.size(); i < c; i++) { + auto& item = materialConstants[i]; + // the key can be a string_view because mMaterialConstant owns the CString + std::string_view const key{ item.name.data(), item.name.size() }; + specializationConstantsNameToIndex[key] = i; + } +} + +void MaterialDefinition::processDescriptorSets(FEngine& engine) { + UTILS_UNUSED_IN_RELEASE bool success; + + success = materialParser->getDescriptorBindings(&programDescriptorBindings); + assert_invariant(success); + + backend::DescriptorSetLayout descriptorSetLayout; + success = materialParser->getDescriptorSetLayout(&descriptorSetLayout); + assert_invariant(success); + + // get the PER_VIEW descriptor binding info + bool const isLit = isVariantLit || hasShadowMultiplier; + bool const isSSR = reflectionMode == ReflectionMode::SCREEN_SPACE || + refractionMode == RefractionMode::SCREEN_SPACE; + bool const hasFog = !(variantFilterMask & UserVariantFilterMask(UserVariantFilterBit::FOG)); + + auto perViewDescriptorSetLayout = descriptor_sets::getPerViewDescriptorSetLayout( + materialDomain, isLit, isSSR, hasFog, false); + + auto perViewDescriptorSetLayoutVsm = descriptor_sets::getPerViewDescriptorSetLayout( + materialDomain, isLit, isSSR, hasFog, true); + + // set the labels + descriptorSetLayout.label = CString{ name }.append("_perMat"); + perViewDescriptorSetLayout.label = CString{ name }.append("_perView"); + perViewDescriptorSetLayoutVsm.label = CString{ name }.append("_perViewVsm"); + + // get the PER_RENDERABLE and PER_VIEW descriptor binding info + for (auto&& [bindingPoint, descriptorSetLayout] : { + std::pair{ DescriptorSetBindingPoints::PER_RENDERABLE, + descriptor_sets::getPerRenderableLayout() }, + std::pair{ DescriptorSetBindingPoints::PER_VIEW, + perViewDescriptorSetLayout }}) { + Program::DescriptorBindingsInfo& descriptors = programDescriptorBindings[+bindingPoint]; + descriptors.reserve(descriptorSetLayout.bindings.size()); + for (auto const& entry: descriptorSetLayout.bindings) { + auto const& name = descriptor_sets::getDescriptorName(bindingPoint, entry.binding); + descriptors.push_back({ name, entry.type, entry.binding }); + } + } + + this->descriptorSetLayout = { + engine.getDescriptorSetLayoutFactory(), + engine.getDriverApi(), std::move(descriptorSetLayout) }; + + this->perViewDescriptorSetLayout = { + engine.getDescriptorSetLayoutFactory(), + engine.getDriverApi(), std::move(perViewDescriptorSetLayout) }; + + this->perViewDescriptorSetLayoutVsm = { + engine.getDescriptorSetLayoutFactory(), + engine.getDriverApi(), std::move(perViewDescriptorSetLayoutVsm) }; +} + +} // namespace filament diff --git a/filament/src/MaterialDefinition.h b/filament/src/MaterialDefinition.h new file mode 100644 index 000000000000..7cf77b3e6559 --- /dev/null +++ b/filament/src/MaterialDefinition.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TNT_FILAMENT_MATERIALDEFINITION_H +#define TNT_FILAMENT_MATERIALDEFINITION_H + +#include +#include +#include +#include +#include + +#include + +#include + +namespace filament { + +class FEngine; +class MaterialParser; + +/** A MaterialDefinition is a parsed, unmarshalled material file, containing no state. + * + * Given that this is a pure read-only class, nearly all members are public without getters. + */ +struct MaterialDefinition { + using BlendingMode = filament::BlendingMode; + using Shading = filament::Shading; + using Interpolation = filament::Interpolation; + using VertexDomain = filament::VertexDomain; + using TransparencyMode = filament::TransparencyMode; + using CullingMode = backend::CullingMode; + + using AttributeInfoContainer = utils::FixedCapacityVector>; + + using BindingUniformInfoContainer = utils::FixedCapacityVector< + std::tuple>; + + // Called by MaterialCache. + static std::unique_ptr createParser(backend::Backend const backend, + utils::FixedCapacityVector languages, + const void* UTILS_NONNULL data, size_t size); + + // Called by MaterialCache. + static std::unique_ptr create(FEngine& engine, + std::unique_ptr parser); + + // public only due to std::make_unique(). + MaterialDefinition(FEngine& engine, std::unique_ptr parser); + + // Free GPU resources owned by this MaterialDefinition. + void terminate(FEngine& engine); + + // try to order by frequency of use + DescriptorSetLayout perViewDescriptorSetLayout; + DescriptorSetLayout perViewDescriptorSetLayoutVsm; + DescriptorSetLayout descriptorSetLayout; + backend::Program::DescriptorSetInfo programDescriptorBindings; + + backend::RasterState rasterState; + TransparencyMode transparencyMode = TransparencyMode::DEFAULT; + bool isVariantLit = false; + backend::FeatureLevel featureLevel = backend::FeatureLevel::FEATURE_LEVEL_1; + Shading shading = Shading::UNLIT; + + BlendingMode blendingMode = BlendingMode::OPAQUE; + std::array customBlendFunctions = {}; + Interpolation interpolation = Interpolation::SMOOTH; + VertexDomain vertexDomain = VertexDomain::OBJECT; + MaterialDomain materialDomain = MaterialDomain::SURFACE; + CullingMode cullingMode = CullingMode::NONE; + AttributeBitset requiredAttributes; + UserVariantFilterMask variantFilterMask = 0; + RefractionMode refractionMode = RefractionMode::NONE; + RefractionType refractionType = RefractionType::SOLID; + ReflectionMode reflectionMode = ReflectionMode::DEFAULT; + uint64_t materialProperties = 0; + uint8_t perViewLayoutIndex = 0; + + float maskThreshold = 0.4f; + float specularAntiAliasingVariance = 0.0f; + float specularAntiAliasingThreshold = 0.0f; + + bool doubleSided = false; + bool doubleSidedCapability = false; + bool hasShadowMultiplier = false; + bool hasCustomDepthShader = false; + bool specularAntiAliasing = false; + + SamplerInterfaceBlock samplerInterfaceBlock; + BufferInterfaceBlock uniformInterfaceBlock; + SubpassInfo subpassInfo; + + BindingUniformInfoContainer bindingUniformInfo; + AttributeInfoContainer attributeInfo; + + // Constants defined by this Material + utils::FixedCapacityVector materialConstants; + // A map from the Constant name to the mMaterialConstant index + std::unordered_map specializationConstantsNameToIndex; + + utils::CString name; + uint64_t cacheId = 0; + std::unique_ptr materialParser; + +private: + void processMain(); + void processBlendingMode(); + void processSpecializationConstants(); + void processDescriptorSets(FEngine& engine); +}; + +} // namespace filament + +#endif // TNT_FILAMENT_MATERIALDEFINITION_H diff --git a/filament/src/MaterialParser.cpp b/filament/src/MaterialParser.cpp index ea2cb1c47a42..caa50382d7a1 100644 --- a/filament/src/MaterialParser.cpp +++ b/filament/src/MaterialParser.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -162,6 +163,19 @@ MaterialParser::ParseResult MaterialParser::parse() noexcept { return ParseResult::SUCCESS; } +uint32_t MaterialParser::computeCrc32() const noexcept { + const size_t size = mImpl.mManagedBuffer.size(); + const void* const UTILS_NONNULL payload = mImpl.mManagedBuffer.data(); + + constexpr size_t crc32ChunkSize = sizeof(uint64_t) + sizeof(uint32_t) + sizeof(uint32_t); + const size_t originalSize = size - crc32ChunkSize; + assert_invariant(size > crc32ChunkSize); + + std::vector crc32Table; + utils::hash::crc32GenerateTable(crc32Table); + return utils::hash::crc32Update(0, payload, originalSize, crc32Table); +} + ShaderLanguage MaterialParser::getShaderLanguage() const noexcept { return mImpl.mChosenLanguage; } @@ -396,7 +410,7 @@ bool MaterialParser::getReflectionMode(ReflectionMode* value) const noexcept { } bool MaterialParser::getShader(ShaderContent& shader, - ShaderModel const shaderModel, Variant const variant, ShaderStage const stage) noexcept { + ShaderModel const shaderModel, Variant const variant, ShaderStage const stage) const noexcept { return mImpl.mMaterialChunk.getShader(shader, mImpl.mBlobDictionary, shaderModel, variant, stage); } diff --git a/filament/src/MaterialParser.h b/filament/src/MaterialParser.h index 5f8039be48dc..eefbaffcb36d 100644 --- a/filament/src/MaterialParser.h +++ b/filament/src/MaterialParser.h @@ -66,6 +66,8 @@ class MaterialParser { }; ParseResult parse() noexcept; + + uint32_t computeCrc32() const noexcept; backend::ShaderLanguage getShaderLanguage() const noexcept; // Accessors @@ -129,7 +131,7 @@ class MaterialParser { bool getMaterialCrc32(uint32_t* value) const noexcept; bool getShader(filaflat::ShaderContent& shader, backend::ShaderModel shaderModel, - Variant variant, backend::ShaderStage stage) noexcept; + Variant variant, backend::ShaderStage stage) const noexcept; bool hasShader(backend::ShaderModel const model, Variant const variant, backend::ShaderStage const stage) const noexcept { diff --git a/filament/src/RefCountedMap.h b/filament/src/RefCountedMap.h new file mode 100644 index 000000000000..5d7b1df35750 --- /dev/null +++ b/filament/src/RefCountedMap.h @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TNT_FILAMENT_REFCOUNTEDMAP_H +#define TNT_FILAMENT_REFCOUNTEDMAP_H + +#include +#include +#include + +#include + +namespace filament { + +namespace refcountedmap { + +template +concept IsPointer = requires(T a) { *a; }; + +} // namespace refcountedmap + +/** A reference-counted map. + * + * Don't use RAII here, both because we sometimes want to deliberately leak memory, and because + * we're managing GL resources that require more managed destruction. + */ +template> +class RefCountedMap { + // Use references for the key if the size of the key type is greater than the size of a pointer. + using KeyRef = std::conditional_t<(sizeof(Key) > sizeof(void*)), const Key&, Key>; + // If T is a pointer type, get the type of the value it points to; otherwise, T. + using TValue = typename std::conditional_t, + typename std::pointer_traits::element_type, T>; + + struct Entry { + uint32_t referenceCount; + T value; + }; + + using Map = tsl::robin_map; + + static constexpr TValue& deref(T& a) { + if constexpr (refcountedmap::IsPointer) { + return *a; + } else { + return a; + } + } + + static constexpr const char* UTILS_NONNULL MISSING_ENTRY_ERROR_STRING = + "Cache is missing entry"; + +public: + /** Acquire a new reference to value by key, initializing it with F if it doesn't exist. + * + * If T is a pointer type, F returns T, where nullptr indicates a failure to create the object. + * Otherwise, F returns std::optional, where nullopt indicates a failure to create the + * object, and the returned pointer is valid only as long as the next call to acquire() or + * release(). + */ + template + TValue* UTILS_NULLABLE acquire(KeyRef key, size_t hash, F factory) noexcept { + auto it = mMap.find(key, hash); + if (it != mMap.end()) { + it.value().referenceCount++; + return &deref(it.value().value); + } + if constexpr (refcountedmap::IsPointer) { + T r = factory(); + if (r) { + // TODO: how to use above computed hash here? + return &*mMap.insert({key, Entry{ + .referenceCount = 1, + .value = std::move(r), + }}).first.value().value; + } + return nullptr; + } else { + std::optional r = factory(); + if (r) { + // TODO: how to use above computed hash here? + return &mMap.insert({key, Entry{ + .referenceCount = 1, + .value = std::move(r.value()), + }}).first.value().value; + } + return nullptr; + } + } + + template + inline TValue* UTILS_NULLABLE acquire(KeyRef key, F factory) noexcept { + return acquire(key, Hash{}(key), std::move(factory)); + } + + /** Acquire a reference to value by key, panicking if it doesn't exist. + * + * This reference is valid only as long as the next call to acquire() or release(). + */ + TValue& acquire(KeyRef key, size_t hash) noexcept { + auto it = mMap.find(key, hash); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + it.value().referenceCount++; + return deref(it.value().value); + } + + inline TValue& acquire(KeyRef key) noexcept { + return acquire(key, Hash{}(key)); + } + + /** Release a reference to key, destroying it with F if reference count reaches zero. + * + * Panics if no entry found in map. + */ + template + void release(KeyRef key, size_t hash, F releaser) noexcept { + auto it = mMap.find(key, hash); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + if (--it.value().referenceCount == 0) { + releaser(deref(it.value().value)); + // TODO: change to erase_fast + mMap.erase(it); + } + } + + template + inline void release(KeyRef key, F releaser) noexcept { + release(key, Hash{}(key), std::move(releaser)); + } + + /** Release a reference to key. + * + * Panics if no entry found in map. + */ + void release(KeyRef key, size_t hash) noexcept { + auto it = mMap.find(key, hash); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + if (--it.value().referenceCount == 0) { + // TODO: change to erase_fast + mMap.erase(it); + } + } + + inline void release(KeyRef key) noexcept { + release(key, Hash{}(key)); + } + + /** Return reference to existing value by key. + * + * This reference is valid only as long as the next call to acquire() or release(). + * + * Panics if no entry found in map. + */ + T& get(KeyRef key, size_t hash) noexcept { + auto it = mMap.find(key); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + return deref(it.value().value); + } + + inline TValue& get(KeyRef key) noexcept { return get(key, Hash{}(key)); } + + TValue const& get(KeyRef key, size_t hash) const noexcept { + auto it = mMap.find(key); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + return deref(it->second.value); + } + + inline TValue const& get(KeyRef key) const noexcept { return get(key, Hash{}(key)); } + + /** Returns true if the map is empty. */ + inline bool empty() const noexcept { return mMap.empty(); } + +private: + Map mMap; +}; + +} + +#endif // TNT_FILAMENT_REFCOUNTEDMAP_H diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index 4f1bb4bbaf4b..0c444718e5c2 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -908,8 +908,8 @@ FIndirectLight* FEngine::createIndirectLight(const IndirectLight::Builder& build } FMaterial* FEngine::createMaterial(const Material::Builder& builder, - std::unique_ptr materialParser) noexcept { - return create(mMaterials, builder, std::move(materialParser)); + MaterialDefinition const& definition) noexcept { + return create(mMaterials, builder, definition); } FSkybox* FEngine::createSkybox(const Skybox::Builder& builder) noexcept { diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index 232d9d9baff4..2228e081a42b 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -25,6 +25,7 @@ #include "ResourceList.h" #include "HwDescriptorSetLayoutFactory.h" #include "HwVertexBufferInfoFactory.h" +#include "MaterialCache.h" #include "components/CameraManager.h" #include "components/LightManager.h" @@ -291,6 +292,10 @@ class FEngine : public Engine { return mResourceAllocatorDisposer; } + MaterialCache& getMaterialCache() const noexcept { + return mMaterialCache; + } + void* streamAlloc(size_t size, size_t alignment) noexcept; Epoch getEngineEpoch() const { return mEngineEpoch; } @@ -312,7 +317,8 @@ class FEngine : public Engine { FMorphTargetBuffer* createMorphTargetBuffer(const MorphTargetBuffer::Builder& builder) noexcept; FInstanceBuffer* createInstanceBuffer(const InstanceBuffer::Builder& builder) noexcept; FIndirectLight* createIndirectLight(const IndirectLight::Builder& builder) noexcept; - FMaterial* createMaterial(const Material::Builder& builder, std::unique_ptr materialParser) noexcept; + FMaterial* createMaterial(const Material::Builder& builder, + MaterialDefinition const& definition) noexcept; FTexture* createTexture(const Texture::Builder& builder) noexcept; FSkybox* createSkybox(const Skybox::Builder& builder) noexcept; FColorGrading* createColorGrading(const ColorGrading::Builder& builder) noexcept; @@ -590,6 +596,7 @@ class FEngine : public Engine { FLightManager mLightManager; FCameraManager mCameraManager; std::shared_ptr mResourceAllocatorDisposer; + mutable MaterialCache mMaterialCache; HwVertexBufferInfoFactory mHwVertexBufferInfoFactory; HwDescriptorSetLayoutFactory mHwDescriptorSetLayoutFactory; DescriptorSetLayout mPerViewDescriptorSetLayoutDepthVariant; diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index 4bf53481069f..d346f389e3d7 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -58,12 +58,8 @@ #include #include -#include -#include -#include #include #include -#include #include #include #include @@ -78,46 +74,6 @@ using namespace backend; using namespace filaflat; using namespace utils; -static std::unique_ptr createParser(Backend const backend, - FixedCapacityVector languages, const void* data, size_t size) { - // unique_ptr so we don't leak MaterialParser on failures below - auto materialParser = std::make_unique(languages, data, size); - - MaterialParser::ParseResult const materialResult = materialParser->parse(); - - if (UTILS_UNLIKELY(materialResult == MaterialParser::ParseResult::ERROR_MISSING_BACKEND)) { - CString languageNames; - for (auto it = languages.begin(); it != languages.end(); ++it) { - languageNames.append(CString{shaderLanguageToString(*it)}); - if (std::next(it) != languages.end()) { - languageNames.append(", "); - } - } - - FILAMENT_CHECK_POSTCONDITION( - materialResult != MaterialParser::ParseResult::ERROR_MISSING_BACKEND) - << "the material was not built for any of the " << to_string(backend) - << " backend's supported shader languages (" << languageNames.c_str() << ")\n"; - } - - if (backend == Backend::NOOP) { - return materialParser; - } - - FILAMENT_CHECK_POSTCONDITION(materialResult == MaterialParser::ParseResult::SUCCESS) - << "could not parse the material package"; - - uint32_t version = 0; - materialParser->getMaterialVersion(&version); - FILAMENT_CHECK_POSTCONDITION(version == MATERIAL_VERSION) - << "Material version mismatch. Expected " << MATERIAL_VERSION << " but received " - << version << "."; - - assert_invariant(backend != Backend::DEFAULT && "Default backend has not been resolved."); - - return materialParser; -} - struct Material::BuilderDetails { const void* mPayload = nullptr; size_t mSize = 0; @@ -180,192 +136,21 @@ const char* toString(ShaderModel model) { } Material* Material::Builder::build(Engine& engine) const { - std::unique_ptr materialParser = createParser( - downcast(engine).getBackend(), downcast(engine).getShaderLanguage(), - mImpl->mPayload, mImpl->mSize); - - if (!materialParser) { - return nullptr; + MaterialDefinition* r = downcast(engine).getMaterialCache().acquire(downcast(engine), + mImpl->mPayload, mImpl->mSize); + if (r) { + return downcast(engine).createMaterial(*this, *r); } - - // Try checking CRC32 value for the package and skip if it's unavailable. - if (downcast(engine).features.material.check_crc32_after_loading) { - uint32_t parsedCrc32 = 0; - materialParser->getMaterialCrc32(&parsedCrc32); - - constexpr size_t crc32ChunkSize = sizeof(uint64_t) + sizeof(uint32_t) + sizeof(uint32_t); - const size_t originalSize = mImpl->mSize - crc32ChunkSize; - assert_invariant(mImpl->mSize > crc32ChunkSize); - - std::vector crc32Table; - hash::crc32GenerateTable(crc32Table); - uint32_t expectedCrc32 = hash::crc32Update(0, mImpl->mPayload, originalSize, crc32Table); - if (parsedCrc32 != expectedCrc32) { - CString name; - materialParser->getName(&name); - LOG(ERROR) << "The material '" << name.c_str_safe() - << "' is corrupted: crc32_expected=" << expectedCrc32 - << ", crc32_parsed=" << parsedCrc32; - return nullptr; - } - } - - uint32_t v = 0; - materialParser->getShaderModels(&v); - bitset32 shaderModels; - shaderModels.setValue(v); - - ShaderModel const shaderModel = downcast(engine).getShaderModel(); - if (!shaderModels.test(static_cast(shaderModel))) { - CString name; - materialParser->getName(&name); - char shaderModelsString[16]; - snprintf(shaderModelsString, sizeof(shaderModelsString), "%#x", shaderModels.getValue()); - LOG(ERROR) << "The material '" << name.c_str_safe() << "' was not built for " - << toString(shaderModel) << "."; - LOG(ERROR) << "Compiled material contains shader models " << shaderModelsString << "."; - return nullptr; - } - - // Print a warning if the material's stereo type doesn't align with the engine's setting. - MaterialDomain materialDomain; - UserVariantFilterMask variantFilterMask; - materialParser->getMaterialDomain(&materialDomain); - materialParser->getMaterialVariantFilterMask(&variantFilterMask); - bool const hasStereoVariants = !(variantFilterMask & UserVariantFilterMask(UserVariantFilterBit::STE)); - if (materialDomain == MaterialDomain::SURFACE && hasStereoVariants) { - StereoscopicType const engineStereoscopicType = engine.getConfig().stereoscopicType; - // Default materials are always compiled with either 'instanced' or 'multiview'. - // So, we only verify compatibility if the engine is set up for stereo. - if (engineStereoscopicType != StereoscopicType::NONE) { - StereoscopicType materialStereoscopicType = StereoscopicType::NONE; - materialParser->getStereoscopicType(&materialStereoscopicType); - if (materialStereoscopicType != engineStereoscopicType) { - CString name; - materialParser->getName(&name); - LOG(WARNING) << "The stereoscopic type in the compiled material '" - << name.c_str_safe() << "' is " << (int) materialStereoscopicType - << ", which is not compatible with the engine's setting " - << (int) engineStereoscopicType << "."; - } - } - } - - return downcast(engine).createMaterial(*this, std::move(materialParser)); + return nullptr; } -FMaterial::FMaterial(FEngine& engine, const Builder& builder, - std::unique_ptr materialParser) - : mIsDefaultMaterial(builder->mDefaultMaterial), +FMaterial::FMaterial(FEngine& engine, const Builder& builder, MaterialDefinition const& definition) + : mDefinition(definition), + mIsDefaultMaterial(builder->mDefaultMaterial), mEngine(engine), - mMaterialId(engine.getMaterialId()), - mMaterialParser(std::move(materialParser)) { - MaterialParser* const parser = mMaterialParser.get(); - - UTILS_UNUSED_IN_RELEASE bool const nameOk = parser->getName(&mName); - assert_invariant(nameOk); - - mFeatureLevel = [parser]() -> FeatureLevel { - // code written this way so the IDE will complain when/if we add a FeatureLevel - uint8_t level = 1; - parser->getFeatureLevel(&level); - assert_invariant(level <= 3); - FeatureLevel featureLevel = FeatureLevel::FEATURE_LEVEL_1; - switch (FeatureLevel(level)) { - case FeatureLevel::FEATURE_LEVEL_0: - case FeatureLevel::FEATURE_LEVEL_1: - case FeatureLevel::FEATURE_LEVEL_2: - case FeatureLevel::FEATURE_LEVEL_3: - featureLevel = FeatureLevel(level); - break; - } - return featureLevel; - }(); - - UTILS_UNUSED_IN_RELEASE bool success; - - success = parser->getCacheId(&mCacheId); - assert_invariant(success); - - success = parser->getSIB(&mSamplerInterfaceBlock); - assert_invariant(success); - - success = parser->getUIB(&mUniformInterfaceBlock); - assert_invariant(success); - - if (UTILS_UNLIKELY(parser->getShaderLanguage() == ShaderLanguage::ESSL1)) { - success = parser->getAttributeInfo(&mAttributeInfo); - assert_invariant(success); - - success = parser->getBindingUniformInfo(&mBindingUniformInfo); - assert_invariant(success); - } - - // Older materials will not have a subpass chunk; this should not be an error. - if (!parser->getSubpasses(&mSubpassInfo)) { - mSubpassInfo.isValid = false; - } - - parser->getShading(&mShading); - parser->getMaterialProperties(&mMaterialProperties); - parser->getInterpolation(&mInterpolation); - parser->getVertexDomain(&mVertexDomain); - parser->getMaterialDomain(&mMaterialDomain); - parser->getMaterialVariantFilterMask(&mVariantFilterMask); - parser->getRequiredAttributes(&mRequiredAttributes); - parser->getRefractionMode(&mRefractionMode); - parser->getRefractionType(&mRefractionType); - parser->getReflectionMode(&mReflectionMode); - parser->getTransparencyMode(&mTransparencyMode); - parser->getDoubleSided(&mDoubleSided); - parser->getCullingMode(&mCullingMode); - - if (mShading == Shading::UNLIT) { - parser->hasShadowMultiplier(&mHasShadowMultiplier); - } - - mIsVariantLit = mShading != Shading::UNLIT || mHasShadowMultiplier; - - // color write - bool colorWrite = false; - parser->getColorWrite(&colorWrite); - mRasterState.colorWrite = colorWrite; - - // depth test - bool depthTest = false; - parser->getDepthTest(&depthTest); - mRasterState.depthFunc = depthTest ? RasterState::DepthFunc::GE : RasterState::DepthFunc::A; - - // if doubleSided() was called we override culling() - bool doubleSideSet = false; - parser->getDoubleSidedSet(&doubleSideSet); - if (doubleSideSet) { - mDoubleSidedCapability = true; - mRasterState.culling = mDoubleSided ? CullingMode::NONE : mCullingMode; - } else { - mRasterState.culling = mCullingMode; - } - - // specular anti-aliasing - parser->hasSpecularAntiAliasing(&mSpecularAntiAliasing); - if (mSpecularAntiAliasing) { - parser->getSpecularAntiAliasingVariance(&mSpecularAntiAliasingVariance); - parser->getSpecularAntiAliasingThreshold(&mSpecularAntiAliasingThreshold); - } - - parser->hasCustomDepthShader(&mHasCustomDepthShader); - - bool const isLit = mIsVariantLit || mHasShadowMultiplier; - bool const isSSR = mReflectionMode == ReflectionMode::SCREEN_SPACE || - mRefractionMode == RefractionMode::SCREEN_SPACE; - bool const hasFog = !(mVariantFilterMask & UserVariantFilterMask(UserVariantFilterBit::FOG)); - - mPerViewLayoutIndex = ColorPassDescriptorSet::getIndex(isLit, isSSR, hasFog); - - processBlendingMode(parser); - processSpecializationConstants(engine, builder, parser); - processPushConstants(engine, parser); - processDescriptorSets(engine, parser); + mMaterialId(engine.getMaterialId()) { + processSpecializationConstants(engine, builder); + processPushConstants(engine); precacheDepthVariants(engine); #if FILAMENT_ENABLE_MATDBG @@ -373,7 +158,7 @@ FMaterial::FMaterial(FEngine& engine, const Builder& builder, matdbg::DebugServer* server = downcast(engine).debug.server; if (UTILS_UNLIKELY(server)) { auto const details = builder.mImpl; - mDebuggerId = server->addMaterial(mName, details->mPayload, details->mSize, this); + mDebuggerId = server->addMaterial(mDefinition.name, details->mPayload, details->mSize, this); } #endif } @@ -383,9 +168,9 @@ FMaterial::~FMaterial() noexcept = default; void FMaterial::invalidate(Variant::type_t variantMask, Variant::type_t variantValue) noexcept { // Note: This API is not public at the moment, so it's okay to have some debugging logs // and extra checks. - if (mMaterialDomain == MaterialDomain::SURFACE && - !mIsDefaultMaterial && - !mHasCustomDepthShader) { + if (mDefinition.materialDomain == MaterialDomain::SURFACE && + !mIsDefaultMaterial && + !mDefinition.hasCustomDepthShader) { // it would be unsafe to invalidate any of the cached depth variant if (UTILS_UNLIKELY(!((variantMask & Variant::DEP) && !(variantValue & Variant::DEP)))) { char variantMaskString[16]; @@ -402,7 +187,6 @@ void FMaterial::invalidate(Variant::type_t variantMask, Variant::type_t variantV } void FMaterial::terminate(FEngine& engine) { - if (mDefaultMaterialInstance) { mDefaultMaterialInstance->setDefaultInstance(false); engine.destroy(mDefaultMaterialInstance); @@ -429,28 +213,24 @@ void FMaterial::terminate(FEngine& engine) { #endif destroyPrograms(engine); - - DriverApi& driver = engine.getDriverApi(); - mPerViewDescriptorSetLayout.terminate(engine.getDescriptorSetLayoutFactory(), driver); - mPerViewDescriptorSetLayoutVsm.terminate(engine.getDescriptorSetLayoutFactory(), driver); - mDescriptorSetLayout.terminate(engine.getDescriptorSetLayoutFactory(), driver); + engine.getMaterialCache().release(engine, mDefinition.cacheId); } filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout( Variant const variant, bool const useVsmDescriptorSetLayout) const noexcept { if (Variant::isValidDepthVariant(variant)) { - assert_invariant(mMaterialDomain == MaterialDomain::SURFACE); + assert_invariant(mDefinition.materialDomain == MaterialDomain::SURFACE); return mEngine.getPerViewDescriptorSetLayoutDepthVariant(); } if (Variant::isSSRVariant(variant)) { - assert_invariant(mMaterialDomain == MaterialDomain::SURFACE); + assert_invariant(mDefinition.materialDomain == MaterialDomain::SURFACE); return mEngine.getPerViewDescriptorSetLayoutSsrVariant(); } if (useVsmDescriptorSetLayout) { - assert_invariant(mMaterialDomain == MaterialDomain::SURFACE); - return mPerViewDescriptorSetLayoutVsm; + assert_invariant(mDefinition.materialDomain == MaterialDomain::SURFACE); + return mDefinition.perViewDescriptorSetLayoutVsm; } - return mPerViewDescriptorSetLayout; + return mDefinition.perViewDescriptorSetLayout; } void FMaterial::compile(CompilerPriorityQueue const priority, @@ -507,25 +287,35 @@ FMaterialInstance* FMaterial::createInstance(const char* name) const noexcept { FMaterialInstance* FMaterial::getDefaultInstance() noexcept { if (UTILS_UNLIKELY(!mDefaultMaterialInstance)) { - mDefaultMaterialInstance = mEngine.createMaterialInstance(this, mName.c_str()); + mDefaultMaterialInstance = mEngine.createMaterialInstance(this, + mDefinition.name.c_str()); mDefaultMaterialInstance->setDefaultInstance(true); } return mDefaultMaterialInstance; } bool FMaterial::hasParameter(const char* name) const noexcept { - return mUniformInterfaceBlock.hasField(name) || - mSamplerInterfaceBlock.hasSampler(name) || - mSubpassInfo.name == CString(name); + return mDefinition.uniformInterfaceBlock.hasField(name) || + mDefinition.samplerInterfaceBlock.hasSampler(name) || + mDefinition.subpassInfo.name == CString(name); } bool FMaterial::isSampler(const char* name) const noexcept { - return mSamplerInterfaceBlock.hasSampler(name); + return mDefinition.samplerInterfaceBlock.hasSampler(name); } BufferInterfaceBlock::FieldInfo const* FMaterial::reflect( std::string_view const name) const noexcept { - return mUniformInterfaceBlock.getFieldInfo(name); + return mDefinition.uniformInterfaceBlock.getFieldInfo(name); +} + +MaterialParser const& FMaterial::getMaterialParser() const noexcept { +#if FILAMENT_ENABLE_MATDBG + if (mEditedMaterialParser) { + return *mEditedMaterialParser; + } +#endif + return *mDefinition.materialParser; } bool FMaterial::hasVariant(Variant const variant) const noexcept { @@ -543,10 +333,10 @@ bool FMaterial::hasVariant(Variant const variant) const noexcept { return false; } const ShaderModel sm = mEngine.getShaderModel(); - if (!mMaterialParser->hasShader(sm, vertexVariant, ShaderStage::VERTEX)) { + if (!mDefinition.materialParser->hasShader(sm, vertexVariant, ShaderStage::VERTEX)) { return false; } - if (!mMaterialParser->hasShader(sm, fragmentVariant, ShaderStage::FRAGMENT)) { + if (!mDefinition.materialParser->hasShader(sm, fragmentVariant, ShaderStage::FRAGMENT)) { return false; } return true; @@ -554,7 +344,7 @@ bool FMaterial::hasVariant(Variant const variant) const noexcept { void FMaterial::prepareProgramSlow(Variant const variant, backend::CompilerPriorityQueue const priorityQueue) const noexcept { - assert_invariant(mEngine.hasFeatureLevel(mFeatureLevel)); + assert_invariant(mEngine.hasFeatureLevel(mDefinition.featureLevel)); switch (getMaterialDomain()) { case MaterialDomain::SURFACE: getSurfaceProgramSlow(variant, priorityQueue); @@ -605,13 +395,15 @@ Program FMaterial::getProgramWithVariants( * Vertex shader */ + MaterialParser const& parser = getMaterialParser(); + ShaderContent& vsBuilder = engine.getVertexShaderContent(); - UTILS_UNUSED_IN_RELEASE bool const vsOK = mMaterialParser->getShader(vsBuilder, sm, + UTILS_UNUSED_IN_RELEASE bool const vsOK = parser.getShader(vsBuilder, sm, vertexVariant, ShaderStage::VERTEX); FILAMENT_CHECK_POSTCONDITION(isNoop || (vsOK && !vsBuilder.empty())) - << "The material '" << mName.c_str() + << "The material '" << mDefinition.name.c_str() << "' has not been compiled to include the required GLSL or SPIR-V chunks for the " "vertex shader (variant=" << +variant.key << ", filtered=" << +vertexVariant.key << ")."; @@ -622,11 +414,11 @@ Program FMaterial::getProgramWithVariants( ShaderContent& fsBuilder = engine.getFragmentShaderContent(); - UTILS_UNUSED_IN_RELEASE bool const fsOK = mMaterialParser->getShader(fsBuilder, sm, + UTILS_UNUSED_IN_RELEASE bool const fsOK = parser.getShader(fsBuilder, sm, fragmentVariant, ShaderStage::FRAGMENT); FILAMENT_CHECK_POSTCONDITION(isNoop || (fsOK && !fsBuilder.empty())) - << "The material '" << mName.c_str() + << "The material '" << mDefinition.name.c_str() << "' has not been compiled to include the required GLSL or SPIR-V chunks for the " "fragment shader (variant=" << +variant.key << ", filtered=" << +fragmentVariant.key << ")."; @@ -634,8 +426,8 @@ Program FMaterial::getProgramWithVariants( Program program; program.shader(ShaderStage::VERTEX, vsBuilder.data(), vsBuilder.size()) .shader(ShaderStage::FRAGMENT, fsBuilder.data(), fsBuilder.size()) - .shaderLanguage(mMaterialParser->getShaderLanguage()) - .diagnostics(mName, + .shaderLanguage(parser.getShaderLanguage()) + .diagnostics(mDefinition.name, [variant, vertexVariant, fragmentVariant](utils::CString const& name, io::ostream& out) -> io::ostream& { return out << name.c_str_safe() << ", variant=(" << io::hex << +variant.key @@ -644,26 +436,26 @@ Program FMaterial::getProgramWithVariants( << io::hex << +fragmentVariant.key << io::dec << ")"; }); - if (UTILS_UNLIKELY(mMaterialParser->getShaderLanguage() == ShaderLanguage::ESSL1)) { - assert_invariant(!mBindingUniformInfo.empty()); - for (auto const& [index, name, uniforms] : mBindingUniformInfo) { + if (UTILS_UNLIKELY(parser.getShaderLanguage() == ShaderLanguage::ESSL1)) { + assert_invariant(!mDefinition.bindingUniformInfo.empty()); + for (auto const& [index, name, uniforms] : mDefinition.bindingUniformInfo) { program.uniforms(uint32_t(index), name, uniforms); } - program.attributes(mAttributeInfo); + program.attributes(mDefinition.attributeInfo); } program.descriptorBindings(+DescriptorSetBindingPoints::PER_VIEW, - mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_VIEW]); + mDefinition.programDescriptorBindings[+DescriptorSetBindingPoints::PER_VIEW]); program.descriptorBindings(+DescriptorSetBindingPoints::PER_RENDERABLE, - mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_RENDERABLE]); + mDefinition.programDescriptorBindings[+DescriptorSetBindingPoints::PER_RENDERABLE]); program.descriptorBindings(+DescriptorSetBindingPoints::PER_MATERIAL, - mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_MATERIAL]); + mDefinition.programDescriptorBindings[+DescriptorSetBindingPoints::PER_MATERIAL]); program.specializationConstants(mSpecializationConstants); program.pushConstants(ShaderStage::VERTEX, mPushConstants[uint8_t(ShaderStage::VERTEX)]); program.pushConstants(ShaderStage::FRAGMENT, mPushConstants[uint8_t(ShaderStage::FRAGMENT)]); - program.cacheId(hash::combine(size_t(mCacheId), variant.key)); + program.cacheId(hash::combine(size_t(mDefinition.cacheId), variant.key)); return program; } @@ -686,7 +478,7 @@ void FMaterial::createAndCacheProgram(Program&& p, Variant const variant) const } } - auto const program = driverApi.createProgram(std::move(p), CString{ mName }); + auto const program = driverApi.createProgram(std::move(p), CString{ mDefinition.name }); assert_invariant(program); mCachedPrograms[variant.key] = program; @@ -704,7 +496,7 @@ void FMaterial::createAndCacheProgram(Program&& p, Variant const variant) const size_t FMaterial::getParameters(ParameterInfo* parameters, size_t count) const noexcept { count = std::min(count, getParameterCount()); - const auto& uniforms = mUniformInterfaceBlock.getFieldInfoList(); + const auto& uniforms = mDefinition.uniformInterfaceBlock.getFieldInfoList(); size_t i = 0; size_t const uniformCount = std::min(count, size_t(uniforms.size())); for ( ; i < uniformCount; i++) { @@ -718,7 +510,7 @@ size_t FMaterial::getParameters(ParameterInfo* parameters, size_t count) const n info.precision = uniformInfo.precision; } - const auto& samplers = mSamplerInterfaceBlock.getSamplerInfoList(); + const auto& samplers = mDefinition.samplerInterfaceBlock.getSamplerInfoList(); size_t const samplerCount = samplers.size(); for (size_t j = 0; i < count && j < samplerCount; i++, j++) { ParameterInfo& info = parameters[i]; @@ -731,14 +523,14 @@ size_t FMaterial::getParameters(ParameterInfo* parameters, size_t count) const n info.precision = samplerInfo.precision; } - if (mSubpassInfo.isValid && i < count) { + if (mDefinition.subpassInfo.isValid && i < count) { ParameterInfo& info = parameters[i]; - info.name = mSubpassInfo.name.c_str(); + info.name = mDefinition.subpassInfo.name.c_str(); info.isSampler = false; info.isSubpass = true; - info.subpassType = mSubpassInfo.type; + info.subpassType = mDefinition.subpassInfo.type; info.count = 1; - info.precision = mSubpassInfo.precision; + info.precision = mDefinition.subpassInfo.precision; } return count; @@ -751,7 +543,7 @@ size_t FMaterial::getParameters(ParameterInfo* parameters, size_t count) const n // Material Debugger is attached. The only editable features of a material package are the shader // source strings, so here we trigger a rebuild of the HwProgram objects. void FMaterial::applyPendingEdits() noexcept { - const char* name = mName.c_str(); + const char* name = mDefinition.name.c_str(); DLOG(INFO) << "Applying edits to " << (name ? name : "(untitled)"); destroyPrograms(mEngine); // FIXME: this will not destroy the shared variants latchPendingEdits(); @@ -769,7 +561,7 @@ bool FMaterial::hasPendingEdits() const noexcept { void FMaterial::latchPendingEdits() noexcept { std::lock_guard const lock(mPendingEditsLock); - mMaterialParser = std::move(mPendingEdits); + mEditedMaterialParser = std::move(mPendingEdits); } /** @@ -786,7 +578,7 @@ void FMaterial::onEditCallback(void* userdata, const CString&, const void* packa // This is called on a web server thread, so we defer clearing the program cache // and swapping out the MaterialParser until the next getProgram call. - std::unique_ptr pending = createParser( + std::unique_ptr pending = MaterialDefinition::createParser( engine.getBackend(), engine.getShaderLanguage(), packageData, packageSize); material->setPendingEdits(std::move(pending)); } @@ -832,9 +624,9 @@ void FMaterial::destroyPrograms(FEngine& engine, DriverApi& driverApi = engine.getDriverApi(); auto& cachedPrograms = mCachedPrograms; - switch (mMaterialDomain) { + switch (mDefinition.materialDomain) { case MaterialDomain::SURFACE: { - if (mIsDefaultMaterial || mHasCustomDepthShader) { + if (mIsDefaultMaterial || mDefinition.hasCustomDepthShader) { // default material, or we have custom depth shaders, we destroy all variants for (size_t k = 0, n = VARIANT_COUNT; k < n; ++k) { if ((k & variantMask) == variantValue) { @@ -900,8 +692,8 @@ void FMaterial::destroyPrograms(FEngine& engine, } std::optional FMaterial::getSpecializationConstantId(std::string_view const name) const noexcept { - auto const pos = mSpecializationConstantsNameToIndex.find(name); - if (pos != mSpecializationConstantsNameToIndex.end()) { + auto const pos = mDefinition.specializationConstantsNameToIndex.find(name); + if (pos != mDefinition.specializationConstantsNameToIndex.end()) { return pos->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS; } return std::nullopt; @@ -909,11 +701,11 @@ std::optional FMaterial::getSpecializationConstantId(std::string_view template bool FMaterial::setConstant(uint32_t id, T value) noexcept { - size_t const maxId = mMaterialConstants.size() + CONFIG_MAX_RESERVED_SPEC_CONSTANTS; + size_t const maxId = mDefinition.materialConstants.size() + CONFIG_MAX_RESERVED_SPEC_CONSTANTS; if (UTILS_LIKELY(id < maxId)) { if (id >= CONFIG_MAX_RESERVED_SPEC_CONSTANTS) { // Constant from the material itself (as opposed to the reserved ones) - auto const& constant = mMaterialConstants[id - CONFIG_MAX_RESERVED_SPEC_CONSTANTS]; + auto const& constant = mDefinition.materialConstants[id - CONFIG_MAX_RESERVED_SPEC_CONSTANTS]; using ConstantType = ConstantType; switch (constant.type) { case ConstantType::INT: @@ -946,98 +738,8 @@ bool FMaterial::setConstant(uint32_t id, T value) noexcept { return false; } -void FMaterial::processBlendingMode(MaterialParser const* const parser) { - parser->getBlendingMode(&mBlendingMode); - - if (mBlendingMode == BlendingMode::MASKED) { - parser->getMaskThreshold(&mMaskThreshold); - } - - if (mBlendingMode == BlendingMode::CUSTOM) { - parser->getCustomBlendFunction(&mCustomBlendFunctions); - } - - // blending mode - switch (mBlendingMode) { - // Do not change the MASKED behavior without checking for regressions with - // AlphaBlendModeTest and TextureLinearInterpolationTest, with and without - // View::BlendMode::TRANSLUCENT. - case BlendingMode::MASKED: - case BlendingMode::OPAQUE: - mRasterState.blendFunctionSrcRGB = BlendFunction::ONE; - mRasterState.blendFunctionSrcAlpha = BlendFunction::ONE; - mRasterState.blendFunctionDstRGB = BlendFunction::ZERO; - mRasterState.blendFunctionDstAlpha = BlendFunction::ZERO; - mRasterState.depthWrite = true; - break; - case BlendingMode::TRANSPARENT: - case BlendingMode::FADE: - mRasterState.blendFunctionSrcRGB = BlendFunction::ONE; - mRasterState.blendFunctionSrcAlpha = BlendFunction::ONE; - mRasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA; - mRasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_ALPHA; - mRasterState.depthWrite = false; - break; - case BlendingMode::ADD: - mRasterState.blendFunctionSrcRGB = BlendFunction::ONE; - mRasterState.blendFunctionSrcAlpha = BlendFunction::ONE; - mRasterState.blendFunctionDstRGB = BlendFunction::ONE; - mRasterState.blendFunctionDstAlpha = BlendFunction::ONE; - mRasterState.depthWrite = false; - break; - case BlendingMode::MULTIPLY: - mRasterState.blendFunctionSrcRGB = BlendFunction::ZERO; - mRasterState.blendFunctionSrcAlpha = BlendFunction::ZERO; - mRasterState.blendFunctionDstRGB = BlendFunction::SRC_COLOR; - mRasterState.blendFunctionDstAlpha = BlendFunction::SRC_COLOR; - mRasterState.depthWrite = false; - break; - case BlendingMode::SCREEN: - mRasterState.blendFunctionSrcRGB = BlendFunction::ONE; - mRasterState.blendFunctionSrcAlpha = BlendFunction::ONE; - mRasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_COLOR; - mRasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_COLOR; - mRasterState.depthWrite = false; - break; - case BlendingMode::CUSTOM: - mRasterState.blendFunctionSrcRGB = mCustomBlendFunctions[0]; - mRasterState.blendFunctionSrcAlpha = mCustomBlendFunctions[1]; - mRasterState.blendFunctionDstRGB = mCustomBlendFunctions[2]; - mRasterState.blendFunctionDstAlpha = mCustomBlendFunctions[3]; - mRasterState.depthWrite = false; - } - - // depth write - bool depthWriteSet = false; - parser->getDepthWriteSet(&depthWriteSet); - if (depthWriteSet) { - bool depthWrite = false; - parser->getDepthWrite(&depthWrite); - mRasterState.depthWrite = depthWrite; - } - - // alpha to coverage - bool alphaToCoverageSet = false; - parser->getAlphaToCoverageSet(&alphaToCoverageSet); - if (alphaToCoverageSet) { - bool alphaToCoverage = false; - parser->getAlphaToCoverage(&alphaToCoverage); - mRasterState.alphaToCoverage = alphaToCoverage; - } else { - mRasterState.alphaToCoverage = mBlendingMode == BlendingMode::MASKED; - } -} - -void FMaterial::processSpecializationConstants(FEngine& engine, Builder const& builder, - MaterialParser const* const parser) { - // Older materials won't have a constants chunk, but that's okay. - parser->getConstants(&mMaterialConstants); - for (size_t i = 0, c = mMaterialConstants.size(); i < c; i++) { - auto& item = mMaterialConstants[i]; - // the key can be a string_view because mMaterialConstant owns the CString - std::string_view const key{ item.name.data(), item.name.size() }; - mSpecializationConstantsNameToIndex[key] = i; - } +void FMaterial::processSpecializationConstants(FEngine& engine, Builder const& builder) { + // TODO: migrate some or all of this into MaterialDefinition. // Verify that all the constant specializations exist in the material and that their types match. // The first specialization constants are defined internally by Filament. @@ -1060,7 +762,8 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Builder const& b bool const powerVrShaderWorkarounds = engine.getDriverApi().isWorkaroundNeeded(Workaround::POWER_VR_SHADER_WORKAROUNDS); - mSpecializationConstants.reserve(mMaterialConstants.size() + CONFIG_MAX_RESERVED_SPEC_CONSTANTS); + mSpecializationConstants.reserve( + mDefinition.materialConstants.size() + CONFIG_MAX_RESERVED_SPEC_CONSTANTS); mSpecializationConstants.push_back({ +ReservedSpecializationConstants::BACKEND_FEATURE_LEVEL, int(engine.getSupportedFeatureLevel()) }); @@ -1094,7 +797,8 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Builder const& b mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_SHADOW_SAMPLING_METHOD, int32_t(builder->mShadowSamplingQuality) }); - if (UTILS_UNLIKELY(parser->getShaderLanguage() == ShaderLanguage::ESSL1)) { + if (UTILS_UNLIKELY( + mDefinition.materialParser->getShaderLanguage() == ShaderLanguage::ESSL1)) { // The actual value of this spec-constant is set in the OpenGLDriver backend. mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_SRGB_SWAPCHAIN_EMULATION, @@ -1103,29 +807,29 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Builder const& b for (auto const& [name, value] : builder->mConstantSpecializations) { std::string_view const key{ name.data(), name.size() }; - auto pos = mSpecializationConstantsNameToIndex.find(key); - FILAMENT_CHECK_PRECONDITION(pos != mSpecializationConstantsNameToIndex.end()) - << "The material " << mName.c_str_safe() + auto pos = mDefinition.specializationConstantsNameToIndex.find(key); + FILAMENT_CHECK_PRECONDITION(pos != mDefinition.specializationConstantsNameToIndex.end()) + << "The material " << mDefinition.name.c_str_safe() << " does not have a constant parameter named " << name.c_str() << "."; constexpr char const* const types[3] = {"an int", "a float", "a bool"}; - auto const& constant = mMaterialConstants[pos->second]; + auto const& constant = mDefinition.materialConstants[pos->second]; switch (constant.type) { case ConstantType::INT: FILAMENT_CHECK_PRECONDITION(std::holds_alternative(value)) << "The constant parameter " << name.c_str() << " on material " - << mName.c_str_safe() << " is of type int, but " << types[value.index()] + << mDefinition.name.c_str_safe() << " is of type int, but " << types[value.index()] << " was provided."; break; case ConstantType::FLOAT: FILAMENT_CHECK_PRECONDITION(std::holds_alternative(value)) << "The constant parameter " << name.c_str() << " on material " - << mName.c_str_safe() << " is of type float, but " << types[value.index()] + << mDefinition.name.c_str_safe() << " is of type float, but " << types[value.index()] << " was provided."; break; case ConstantType::BOOL: FILAMENT_CHECK_PRECONDITION(std::holds_alternative(value)) << "The constant parameter " << name.c_str() << " on material " - << mName.c_str_safe() << " is of type bool, but " << types[value.index()] + << mDefinition.name.c_str_safe() << " is of type bool, but " << types[value.index()] << " was provided."; break; } @@ -1134,7 +838,9 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Builder const& b } } -void FMaterial::processPushConstants(FEngine&, MaterialParser const* parser) { +void FMaterial::processPushConstants(FEngine&) { + // TODO: move some or all of this into MaterialDefinition. + FixedCapacityVector& vertexConstants = mPushConstants[uint8_t(ShaderStage::VERTEX)]; FixedCapacityVector& fragmentConstants = @@ -1142,7 +848,7 @@ void FMaterial::processPushConstants(FEngine&, MaterialParser const* parser) { CString structVarName; FixedCapacityVector pushConstants; - parser->getPushConstants(&structVarName, &pushConstants); + mDefinition.materialParser->getPushConstants(&structVarName, &pushConstants); vertexConstants.reserve(pushConstants.size()); fragmentConstants.reserve(pushConstants.size()); @@ -1196,9 +902,9 @@ void FMaterial::precacheDepthVariants(FEngine& engine) { } // if possible pre-cache all depth variants from the default material - if (mMaterialDomain == MaterialDomain::SURFACE && + if (mDefinition.materialDomain == MaterialDomain::SURFACE && !mIsDefaultMaterial && - !mHasCustomDepthShader) { + !mDefinition.hasCustomDepthShader) { FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial(); assert_invariant(pDefaultMaterial); auto const allDepthVariants = VariantUtils::getDepthVariants(); @@ -1209,63 +915,9 @@ void FMaterial::precacheDepthVariants(FEngine& engine) { } } -void FMaterial::processDescriptorSets(FEngine& engine, MaterialParser const* const parser) { - UTILS_UNUSED_IN_RELEASE bool success; - - success = parser->getDescriptorBindings(&mProgramDescriptorBindings); - assert_invariant(success); - - backend::DescriptorSetLayout descriptorSetLayout; - success = parser->getDescriptorSetLayout(&descriptorSetLayout); - assert_invariant(success); - - // get the PER_VIEW descriptor binding info - bool const isLit = mIsVariantLit || mHasShadowMultiplier; - bool const isSSR = mReflectionMode == ReflectionMode::SCREEN_SPACE || - mRefractionMode == RefractionMode::SCREEN_SPACE; - bool const hasFog = !(mVariantFilterMask & UserVariantFilterMask(UserVariantFilterBit::FOG)); - - auto perViewDescriptorSetLayout = descriptor_sets::getPerViewDescriptorSetLayout( - mMaterialDomain, isLit, isSSR, hasFog, false); - - auto perViewDescriptorSetLayoutVsm = descriptor_sets::getPerViewDescriptorSetLayout( - mMaterialDomain, isLit, isSSR, hasFog, true); - - // set the labels - descriptorSetLayout.label = CString{ mName }.append("_perMat"); - perViewDescriptorSetLayout.label = CString{ mName }.append("_perView"); - perViewDescriptorSetLayoutVsm.label = CString{ mName }.append("_perViewVsm"); - - // get the PER_RENDERABLE and PER_VIEW descriptor binding info - for (auto&& [bindingPoint, descriptorSetLayout] : { - std::pair{ DescriptorSetBindingPoints::PER_RENDERABLE, - descriptor_sets::getPerRenderableLayout() }, - std::pair{ DescriptorSetBindingPoints::PER_VIEW, - perViewDescriptorSetLayout }}) { - Program::DescriptorBindingsInfo& descriptors = mProgramDescriptorBindings[+bindingPoint]; - descriptors.reserve(descriptorSetLayout.bindings.size()); - for (auto const& entry: descriptorSetLayout.bindings) { - auto const& name = descriptor_sets::getDescriptorName(bindingPoint, entry.binding); - descriptors.push_back({ name, entry.type, entry.binding }); - } - } - - mDescriptorSetLayout = { - engine.getDescriptorSetLayoutFactory(), - engine.getDriverApi(), std::move(descriptorSetLayout) }; - - mPerViewDescriptorSetLayout = { - engine.getDescriptorSetLayoutFactory(), - engine.getDriverApi(), std::move(perViewDescriptorSetLayout) }; - - mPerViewDescriptorSetLayoutVsm = { - engine.getDescriptorSetLayoutFactory(), - engine.getDriverApi(), std::move(perViewDescriptorSetLayoutVsm) }; -} - descriptor_binding_t FMaterial::getSamplerBinding( std::string_view const& name) const { - return mSamplerInterfaceBlock.getSamplerInfo(name)->binding; + return mDefinition.samplerInterfaceBlock.getSamplerInfo(name)->binding; } template bool FMaterial::setConstant(uint32_t id, int32_t value) noexcept; diff --git a/filament/src/details/Material.h b/filament/src/details/Material.h index 15d0c6c5fa79..3174d5277cb2 100644 --- a/filament/src/details/Material.h +++ b/filament/src/details/Material.h @@ -46,15 +46,8 @@ #include #include -#include -#include -#include #include #include -#include -#include -#include -#include #include #include @@ -72,7 +65,7 @@ class FEngine; class FMaterial : public Material { public: FMaterial(FEngine& engine, const Builder& builder, - std::unique_ptr materialParser); + MaterialDefinition const& definition); ~FMaterial() noexcept; class DefaultMaterialBuilder : public Builder { @@ -85,12 +78,12 @@ class FMaterial : public Material { // return the uniform interface block for this material const BufferInterfaceBlock& getUniformInterfaceBlock() const noexcept { - return mUniformInterfaceBlock; + return mDefinition.uniformInterfaceBlock; } DescriptorSetLayout const& getPerViewDescriptorSetLayout() const noexcept { - assert_invariant(mMaterialDomain == MaterialDomain::POST_PROCESS); - return mPerViewDescriptorSetLayout; + assert_invariant(mDefinition.materialDomain == MaterialDomain::POST_PROCESS); + return mDefinition.perViewDescriptorSetLayout; } DescriptorSetLayout const& getPerViewDescriptorSetLayout( @@ -101,11 +94,11 @@ class FMaterial : public Material { // also use the default material's layout. DescriptorSetLayout const& getDescriptorSetLayout(Variant variant = {}) const noexcept { if (!isSharedVariant(variant)) { - return mDescriptorSetLayout; + return mDefinition.descriptorSetLayout; } FMaterial const* const pDefaultMaterial = mEngine.getDefaultMaterial(); if (UTILS_UNLIKELY(!pDefaultMaterial)) { - return mDescriptorSetLayout; + return mDefinition.descriptorSetLayout; } return pDefaultMaterial->getDescriptorSetLayout(); } @@ -180,58 +173,58 @@ class FMaterial : public Material { [[nodiscard]] backend::Handle getProgramWithMATDBG(Variant variant) const noexcept; - bool isVariantLit() const noexcept { return mIsVariantLit; } + bool isVariantLit() const noexcept { return mDefinition.isVariantLit; } - const utils::CString& getName() const noexcept { return mName; } - backend::FeatureLevel getFeatureLevel() const noexcept { return mFeatureLevel; } - backend::RasterState getRasterState() const noexcept { return mRasterState; } + const utils::CString& getName() const noexcept { return mDefinition.name; } + backend::FeatureLevel getFeatureLevel() const noexcept { return mDefinition.featureLevel; } + backend::RasterState getRasterState() const noexcept { return mDefinition.rasterState; } uint32_t getId() const noexcept { return mMaterialId; } UserVariantFilterMask getSupportedVariants() const noexcept { - return UserVariantFilterMask(UserVariantFilterBit::ALL) & ~mVariantFilterMask; + return UserVariantFilterMask(UserVariantFilterBit::ALL) & ~mDefinition.variantFilterMask; } - Shading getShading() const noexcept { return mShading; } - Interpolation getInterpolation() const noexcept { return mInterpolation; } - BlendingMode getBlendingMode() const noexcept { return mBlendingMode; } - VertexDomain getVertexDomain() const noexcept { return mVertexDomain; } - MaterialDomain getMaterialDomain() const noexcept { return mMaterialDomain; } - CullingMode getCullingMode() const noexcept { return mCullingMode; } - TransparencyMode getTransparencyMode() const noexcept { return mTransparencyMode; } - bool isColorWriteEnabled() const noexcept { return mRasterState.colorWrite; } - bool isDepthWriteEnabled() const noexcept { return mRasterState.depthWrite; } + Shading getShading() const noexcept { return mDefinition.shading; } + Interpolation getInterpolation() const noexcept { return mDefinition.interpolation; } + BlendingMode getBlendingMode() const noexcept { return mDefinition.blendingMode; } + VertexDomain getVertexDomain() const noexcept { return mDefinition.vertexDomain; } + MaterialDomain getMaterialDomain() const noexcept { return mDefinition.materialDomain; } + CullingMode getCullingMode() const noexcept { return mDefinition.cullingMode; } + TransparencyMode getTransparencyMode() const noexcept { return mDefinition.transparencyMode; } + bool isColorWriteEnabled() const noexcept { return mDefinition.rasterState.colorWrite; } + bool isDepthWriteEnabled() const noexcept { return mDefinition.rasterState.depthWrite; } bool isDepthCullingEnabled() const noexcept { - return mRasterState.depthFunc != backend::RasterState::DepthFunc::A; + return mDefinition.rasterState.depthFunc != backend::RasterState::DepthFunc::A; } - bool isDoubleSided() const noexcept { return mDoubleSided; } - bool hasDoubleSidedCapability() const noexcept { return mDoubleSidedCapability; } - bool isAlphaToCoverageEnabled() const noexcept { return mRasterState.alphaToCoverage; } - float getMaskThreshold() const noexcept { return mMaskThreshold; } - bool hasShadowMultiplier() const noexcept { return mHasShadowMultiplier; } - AttributeBitset getRequiredAttributes() const noexcept { return mRequiredAttributes; } - RefractionMode getRefractionMode() const noexcept { return mRefractionMode; } - RefractionType getRefractionType() const noexcept { return mRefractionType; } - ReflectionMode getReflectionMode() const noexcept { return mReflectionMode; } - - bool hasSpecularAntiAliasing() const noexcept { return mSpecularAntiAliasing; } - float getSpecularAntiAliasingVariance() const noexcept { return mSpecularAntiAliasingVariance; } - float getSpecularAntiAliasingThreshold() const noexcept { return mSpecularAntiAliasingThreshold; } + bool isDoubleSided() const noexcept { return mDefinition.doubleSided; } + bool hasDoubleSidedCapability() const noexcept { return mDefinition.doubleSidedCapability; } + bool isAlphaToCoverageEnabled() const noexcept { return mDefinition.rasterState.alphaToCoverage; } + float getMaskThreshold() const noexcept { return mDefinition.maskThreshold; } + bool hasShadowMultiplier() const noexcept { return mDefinition.hasShadowMultiplier; } + AttributeBitset getRequiredAttributes() const noexcept { return mDefinition.requiredAttributes; } + RefractionMode getRefractionMode() const noexcept { return mDefinition.refractionMode; } + RefractionType getRefractionType() const noexcept { return mDefinition.refractionType; } + ReflectionMode getReflectionMode() const noexcept { return mDefinition.reflectionMode; } + + bool hasSpecularAntiAliasing() const noexcept { return mDefinition.specularAntiAliasing; } + float getSpecularAntiAliasingVariance() const noexcept { return mDefinition.specularAntiAliasingVariance; } + float getSpecularAntiAliasingThreshold() const noexcept { return mDefinition.specularAntiAliasingThreshold; } backend::descriptor_binding_t getSamplerBinding( std::string_view const& name) const; bool hasMaterialProperty(Property property) const noexcept { - return bool(mMaterialProperties & uint64_t(property)); + return bool(mDefinition.materialProperties & uint64_t(property)); } SamplerInterfaceBlock const& getSamplerInterfaceBlock() const noexcept { - return mSamplerInterfaceBlock; + return mDefinition.samplerInterfaceBlock; } size_t getParameterCount() const noexcept { - return mUniformInterfaceBlock.getFieldInfoList().size() + - mSamplerInterfaceBlock.getSamplerInfoList().size() + - (mSubpassInfo.isValid ? 1 : 0); + return mDefinition.uniformInterfaceBlock.getFieldInfoList().size() + + mDefinition.samplerInterfaceBlock.getSamplerInfoList().size() + + (mDefinition.subpassInfo.isValid ? 1 : 0); } size_t getParameters(ParameterInfo* parameters, size_t count) const noexcept; @@ -250,7 +243,7 @@ class FMaterial : public Material { bool setConstant(uint32_t id, T value) noexcept; uint8_t getPerViewLayoutIndex() const noexcept { - return mPerViewLayoutIndex; + return mDefinition.perViewLayoutIndex; } #if FILAMENT_ENABLE_MATDBG @@ -286,6 +279,8 @@ class FMaterial : public Material { #endif private: + MaterialParser const& getMaterialParser() const noexcept; + bool hasVariant(Variant variant) const noexcept; void prepareProgramSlow(Variant variant, CompilerPriorityQueue priorityQueue) const noexcept; @@ -296,82 +291,25 @@ class FMaterial : public Material { backend::Program getProgramWithVariants(Variant variant, Variant vertexVariant, Variant fragmentVariant) const; - void processBlendingMode(MaterialParser const* parser); - - void processSpecializationConstants(FEngine& engine, Builder const& builder, - MaterialParser const* parser); - - void processPushConstants(FEngine& engine, MaterialParser const* parser); - + void processSpecializationConstants(FEngine& engine, Builder const& builder); + void processPushConstants(FEngine& engine); void precacheDepthVariants(FEngine& engine); - void processDescriptorSets(FEngine& engine, MaterialParser const* parser); - void createAndCacheProgram(backend::Program&& p, Variant variant) const noexcept; inline bool isSharedVariant(Variant const variant) const { - return (mMaterialDomain == MaterialDomain::SURFACE) && !mIsDefaultMaterial && - !mHasCustomDepthShader && Variant::isValidDepthVariant(variant); + return (mDefinition.materialDomain == MaterialDomain::SURFACE) && !mIsDefaultMaterial && + !mDefinition.hasCustomDepthShader && Variant::isValidDepthVariant(variant); } - // try to order by frequency of use mutable std::array, VARIANT_COUNT> mCachedPrograms; - DescriptorSetLayout mPerViewDescriptorSetLayout; - DescriptorSetLayout mPerViewDescriptorSetLayoutVsm; - DescriptorSetLayout mDescriptorSetLayout; - backend::Program::DescriptorSetInfo mProgramDescriptorBindings; - - backend::RasterState mRasterState; - TransparencyMode mTransparencyMode = TransparencyMode::DEFAULT; - bool mIsVariantLit = false; - backend::FeatureLevel mFeatureLevel = backend::FeatureLevel::FEATURE_LEVEL_1; - Shading mShading = Shading::UNLIT; - - BlendingMode mBlendingMode = BlendingMode::OPAQUE; - std::array mCustomBlendFunctions = {}; - Interpolation mInterpolation = Interpolation::SMOOTH; - VertexDomain mVertexDomain = VertexDomain::OBJECT; - MaterialDomain mMaterialDomain = MaterialDomain::SURFACE; - CullingMode mCullingMode = CullingMode::NONE; - AttributeBitset mRequiredAttributes; - UserVariantFilterMask mVariantFilterMask = 0; - RefractionMode mRefractionMode = RefractionMode::NONE; - RefractionType mRefractionType = RefractionType::SOLID; - ReflectionMode mReflectionMode = ReflectionMode::DEFAULT; - uint64_t mMaterialProperties = 0; - uint8_t mPerViewLayoutIndex = 0; - - float mMaskThreshold = 0.4f; - float mSpecularAntiAliasingVariance = 0.0f; - float mSpecularAntiAliasingThreshold = 0.0f; - - bool mDoubleSided = false; - bool mDoubleSidedCapability = false; - bool mHasShadowMultiplier = false; - bool mHasCustomDepthShader = false; + MaterialDefinition const& mDefinition; + bool mIsDefaultMaterial = false; - bool mSpecularAntiAliasing = false; // reserve some space to construct the default material instance mutable FMaterialInstance* mDefaultMaterialInstance = nullptr; - SamplerInterfaceBlock mSamplerInterfaceBlock; - BufferInterfaceBlock mUniformInterfaceBlock; - SubpassInfo mSubpassInfo; - - using BindingUniformInfoContainer = utils::FixedCapacityVector>; - - BindingUniformInfoContainer mBindingUniformInfo; - - using AttributeInfoContainer = utils::FixedCapacityVector>; - - AttributeInfoContainer mAttributeInfo; - - // Constants defined by this Material - utils::FixedCapacityVector mMaterialConstants; - // A map from the Constant name to the mMaterialConstant index - std::unordered_map mSpecializationConstantsNameToIndex; // current specialization constants for the HwProgram utils::FixedCapacityVector mSpecializationConstants; @@ -386,17 +324,15 @@ class FMaterial : public Material { mutable VariantList mActivePrograms; mutable utils::Mutex mPendingEditsLock; std::unique_ptr mPendingEdits; + std::unique_ptr mEditedMaterialParser; void setPendingEdits(std::unique_ptr pendingEdits) noexcept; bool hasPendingEdits() const noexcept; void latchPendingEdits() noexcept; #endif - utils::CString mName; FEngine& mEngine; const uint32_t mMaterialId; - uint64_t mCacheId = 0; mutable uint32_t mMaterialInstanceId = 0; - std::unique_ptr mMaterialParser; }; diff --git a/libs/filaflat/include/filaflat/MaterialChunk.h b/libs/filaflat/include/filaflat/MaterialChunk.h index 304819769eef..0f46b62a0634 100644 --- a/libs/filaflat/include/filaflat/MaterialChunk.h +++ b/libs/filaflat/include/filaflat/MaterialChunk.h @@ -48,7 +48,7 @@ class MaterialChunk { // call this as many times as needed // populates "shaderContent" with the requested shader, or returns false on failure. bool getShader(ShaderContent& shaderContent, BlobDictionary const& dictionary, - ShaderModel shaderModel, filament::Variant variant, ShaderStage stage); + ShaderModel shaderModel, filament::Variant variant, ShaderStage stage) const; uint32_t getShaderCount() const noexcept; @@ -72,11 +72,11 @@ class MaterialChunk { bool getTextShader(Unflattener unflattener, BlobDictionary const& dictionary, ShaderContent& shaderContent, - ShaderModel shaderModel, filament::Variant variant, ShaderStage shaderStage); + ShaderModel shaderModel, filament::Variant variant, ShaderStage shaderStage) const; bool getBinaryShader( BlobDictionary const& dictionary, ShaderContent& shaderContent, - ShaderModel shaderModel, filament::Variant variant, ShaderStage shaderStage); + ShaderModel shaderModel, filament::Variant variant, ShaderStage shaderStage) const; }; } // namespace filamat diff --git a/libs/filaflat/src/MaterialChunk.cpp b/libs/filaflat/src/MaterialChunk.cpp index cd795444f116..5d26423aa94f 100644 --- a/libs/filaflat/src/MaterialChunk.cpp +++ b/libs/filaflat/src/MaterialChunk.cpp @@ -101,7 +101,7 @@ bool MaterialChunk::initialize(filamat::ChunkType materialTag) { bool MaterialChunk::getTextShader(Unflattener unflattener, BlobDictionary const& dictionary, ShaderContent& shaderContent, - ShaderModel shaderModel, Variant variant, ShaderStage shaderStage) { + ShaderModel shaderModel, Variant variant, ShaderStage shaderStage) const { if (mBase == nullptr) { return false; } @@ -157,7 +157,7 @@ bool MaterialChunk::getTextShader(Unflattener unflattener, } bool MaterialChunk::getBinaryShader(BlobDictionary const& dictionary, - ShaderContent& shaderContent, ShaderModel shaderModel, filament::Variant variant, ShaderStage shaderStage) { + ShaderContent& shaderContent, ShaderModel shaderModel, filament::Variant variant, ShaderStage shaderStage) const { if (mBase == nullptr) { return false; @@ -182,7 +182,7 @@ bool MaterialChunk::hasShader(ShaderModel model, Variant variant, ShaderStage st } bool MaterialChunk::getShader(ShaderContent& shaderContent, BlobDictionary const& dictionary, - ShaderModel shaderModel, filament::Variant variant, ShaderStage stage) { + ShaderModel shaderModel, filament::Variant variant, ShaderStage stage) const { switch (mMaterialTag) { case filamat::ChunkType::MaterialGlsl: case filamat::ChunkType::MaterialEssl1: @@ -233,4 +233,3 @@ void MaterialChunk::visitShaders( } } // namespace filaflat - From 8080a468429b11c608671cfd70f3cf4b705feca1 Mon Sep 17 00:00:00 2001 From: Eliza Velasquez Date: Fri, 12 Sep 2025 14:48:20 -0700 Subject: [PATCH 2/7] material cache: key on crc32 --- filament/src/MaterialCache.cpp | 23 +++++++++++++++-------- filament/src/MaterialCache.h | 24 ++++++++++++++++-------- filament/src/MaterialParser.cpp | 8 ++++++++ filament/src/MaterialParser.h | 2 ++ filament/src/details/Material.cpp | 2 +- 5 files changed, 42 insertions(+), 17 deletions(-) diff --git a/filament/src/MaterialCache.cpp b/filament/src/MaterialCache.cpp index 7a931276dbea..f260bc359429 100644 --- a/filament/src/MaterialCache.cpp +++ b/filament/src/MaterialCache.cpp @@ -26,6 +26,18 @@ namespace filament { +size_t MaterialCache::Key::Hash::operator()(filament::MaterialCache::Key const& x) const noexcept { + uint32_t r; + if (x.parser->getMaterialCrc32(&r)) { + return size_t(r); + } + return size_t(x.parser->computeCrc32()); +} + +bool MaterialCache::Key::operator==(Key const& rhs) const noexcept { + return parser == rhs.parser; +} + MaterialCache::~MaterialCache() { if (!mInner.empty()) { LOG(WARNING) << "MaterialCache was destroyed but wasn't empty"; @@ -40,18 +52,13 @@ MaterialDefinition* UTILS_NULLABLE MaterialCache::acquire(FEngine& engine, return nullptr; } - uint64_t uuid; - if (UTILS_UNLIKELY(!parser->getCacheId(&uuid))) { - return nullptr; - } - - return mInner.acquire(uuid, [&engine, parser = std::move(parser)]() mutable { + return mInner.acquire(Key{parser.get()}, [&engine, parser = std::move(parser)]() mutable { return MaterialDefinition::create(engine, std::move(parser)); }); } -void MaterialCache::release(FEngine& engine, uint64_t uuid) noexcept { - mInner.release(uuid, [&engine](MaterialDefinition& definition) { +void MaterialCache::release(FEngine& engine, MaterialParser const& parser) noexcept { + mInner.release(Key{&parser}, [&engine](MaterialDefinition& definition) { definition.terminate(engine); }); } diff --git a/filament/src/MaterialCache.h b/filament/src/MaterialCache.h index e2577ac02ca7..a265914d0a01 100644 --- a/filament/src/MaterialCache.h +++ b/filament/src/MaterialCache.h @@ -34,11 +34,16 @@ class Material; /** A cache of all materials and shader programs compiled by Filament. */ class MaterialCache { - // Program caches are keyed by the material UUID. - // - // We use unique_ptr here because we need these pointers to be stable. - // TODO: investigate using a custom allocator here? - using Inner = RefCountedMap>; + // A newtype around a material parser used as a key for the material cache. The material file's + // CRC32 is used as the hash function. + struct Key { + struct Hash { + size_t operator()(Key const& x) const noexcept; + }; + bool operator==(Key const& rhs) const noexcept; + + MaterialParser const* UTILS_NONNULL parser; + }; public: ~MaterialCache(); @@ -48,13 +53,16 @@ class MaterialCache { size_t size) noexcept; /** Release an entry in the cache, potentially freeing its GPU resources. */ - void release(FEngine& engine, uint64_t uuid) noexcept; + void release(FEngine& engine, MaterialParser const& parser) noexcept; private: - Inner mInner; + // Program caches are keyed by the material UUID. + // + // We use unique_ptr here because we need these pointers to be stable. + // TODO: investigate using a custom allocator here? + RefCountedMap, Key::Hash> mInner; }; } // namespace filament - #endif // TNT_FILAMENT_MATERIALCACHE_H diff --git a/filament/src/MaterialParser.cpp b/filament/src/MaterialParser.cpp index caa50382d7a1..51e48d7b4872 100644 --- a/filament/src/MaterialParser.cpp +++ b/filament/src/MaterialParser.cpp @@ -107,6 +107,14 @@ MaterialParser::MaterialParserDetails::ManagedBuffer::~ManagedBuffer() noexcept // ------------------------------------------------------------------------------------------------ +bool MaterialParser::operator==(MaterialParser const& rhs) const noexcept { + if (mImpl.mManagedBuffer.size() != rhs.mImpl.mManagedBuffer.size()) { + return false; + } + return !std::memcmp(mImpl.mManagedBuffer.data(), rhs.mImpl.mManagedBuffer.data(), + mImpl.mManagedBuffer.size()); +} + template bool MaterialParser::get(typename T::Container* container) const noexcept { auto [start, end] = mImpl.mChunkContainer.getChunkRange(T::tag); diff --git a/filament/src/MaterialParser.h b/filament/src/MaterialParser.h index eefbaffcb36d..436f5196365c 100644 --- a/filament/src/MaterialParser.h +++ b/filament/src/MaterialParser.h @@ -65,6 +65,8 @@ class MaterialParser { ERROR_OTHER }; + bool operator==(MaterialParser const& rhs) const noexcept; + ParseResult parse() noexcept; uint32_t computeCrc32() const noexcept; diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index d346f389e3d7..0e593c71a977 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -213,7 +213,7 @@ void FMaterial::terminate(FEngine& engine) { #endif destroyPrograms(engine); - engine.getMaterialCache().release(engine, mDefinition.cacheId); + engine.getMaterialCache().release(engine, *mDefinition.materialParser); } filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout( From fd888ef28d5b0bbde50bf71018a66e460e7734a0 Mon Sep 17 00:00:00 2001 From: Eliza Velasquez Date: Wed, 17 Sep 2025 14:43:00 -0700 Subject: [PATCH 3/7] material cache: make materialParser private --- filament/src/MaterialDefinition.cpp | 82 ++++++++++++++--------------- filament/src/MaterialDefinition.h | 7 ++- filament/src/details/Material.cpp | 12 ++--- 3 files changed, 52 insertions(+), 49 deletions(-) diff --git a/filament/src/MaterialDefinition.cpp b/filament/src/MaterialDefinition.cpp index fba8b906b51a..3d8582c68f5d 100644 --- a/filament/src/MaterialDefinition.cpp +++ b/filament/src/MaterialDefinition.cpp @@ -142,7 +142,7 @@ void MaterialDefinition::terminate(FEngine& engine) { MaterialDefinition::MaterialDefinition(FEngine& engine, std::unique_ptr materialParser) - : materialParser(std::move(materialParser)) { + : mMaterialParser(std::move(materialParser)) { processMain(); processBlendingMode(); @@ -151,13 +151,13 @@ MaterialDefinition::MaterialDefinition(FEngine& engine, } void MaterialDefinition::processMain() { - UTILS_UNUSED_IN_RELEASE bool const nameOk = materialParser->getName(&name); + UTILS_UNUSED_IN_RELEASE bool const nameOk = mMaterialParser->getName(&name); assert_invariant(nameOk); featureLevel = [this]() -> FeatureLevel { // code written this way so the IDE will complain when/if we add a FeatureLevel uint8_t level = 1; - materialParser->getFeatureLevel(&level); + mMaterialParser->getFeatureLevel(&level); assert_invariant(level <= 3); FeatureLevel featureLevel = FeatureLevel::FEATURE_LEVEL_1; switch (FeatureLevel(level)) { @@ -173,61 +173,61 @@ void MaterialDefinition::processMain() { UTILS_UNUSED_IN_RELEASE bool success; - success = materialParser->getCacheId(&cacheId); + success = mMaterialParser->getCacheId(&cacheId); assert_invariant(success); - success = materialParser->getSIB(&samplerInterfaceBlock); + success = mMaterialParser->getSIB(&samplerInterfaceBlock); assert_invariant(success); - success = materialParser->getUIB(&uniformInterfaceBlock); + success = mMaterialParser->getUIB(&uniformInterfaceBlock); assert_invariant(success); - if (UTILS_UNLIKELY(materialParser->getShaderLanguage() == ShaderLanguage::ESSL1)) { - success = materialParser->getAttributeInfo(&attributeInfo); + if (UTILS_UNLIKELY(mMaterialParser->getShaderLanguage() == ShaderLanguage::ESSL1)) { + success = mMaterialParser->getAttributeInfo(&attributeInfo); assert_invariant(success); - success = materialParser->getBindingUniformInfo(&bindingUniformInfo); + success = mMaterialParser->getBindingUniformInfo(&bindingUniformInfo); assert_invariant(success); } // Older materials will not have a subpass chunk; this should not be an error. - if (!materialParser->getSubpasses(&subpassInfo)) { + if (!mMaterialParser->getSubpasses(&subpassInfo)) { subpassInfo.isValid = false; } - materialParser->getShading(&shading); - materialParser->getMaterialProperties(&materialProperties); - materialParser->getInterpolation(&interpolation); - materialParser->getVertexDomain(&vertexDomain); - materialParser->getMaterialDomain(&materialDomain); - materialParser->getMaterialVariantFilterMask(&variantFilterMask); - materialParser->getRequiredAttributes(&requiredAttributes); - materialParser->getRefractionMode(&refractionMode); - materialParser->getRefractionType(&refractionType); - materialParser->getReflectionMode(&reflectionMode); - materialParser->getTransparencyMode(&transparencyMode); - materialParser->getDoubleSided(&doubleSided); - materialParser->getCullingMode(&cullingMode); + mMaterialParser->getShading(&shading); + mMaterialParser->getMaterialProperties(&materialProperties); + mMaterialParser->getInterpolation(&interpolation); + mMaterialParser->getVertexDomain(&vertexDomain); + mMaterialParser->getMaterialDomain(&materialDomain); + mMaterialParser->getMaterialVariantFilterMask(&variantFilterMask); + mMaterialParser->getRequiredAttributes(&requiredAttributes); + mMaterialParser->getRefractionMode(&refractionMode); + mMaterialParser->getRefractionType(&refractionType); + mMaterialParser->getReflectionMode(&reflectionMode); + mMaterialParser->getTransparencyMode(&transparencyMode); + mMaterialParser->getDoubleSided(&doubleSided); + mMaterialParser->getCullingMode(&cullingMode); if (shading == Shading::UNLIT) { - materialParser->hasShadowMultiplier(&hasShadowMultiplier); + mMaterialParser->hasShadowMultiplier(&hasShadowMultiplier); } isVariantLit = shading != Shading::UNLIT || hasShadowMultiplier; // color write bool colorWrite = false; - materialParser->getColorWrite(&colorWrite); + mMaterialParser->getColorWrite(&colorWrite); rasterState.colorWrite = colorWrite; // depth test bool depthTest = false; - materialParser->getDepthTest(&depthTest); + mMaterialParser->getDepthTest(&depthTest); rasterState.depthFunc = depthTest ? RasterState::DepthFunc::GE : RasterState::DepthFunc::A; // if doubleSided() was called we override culling() bool doubleSideSet = false; - materialParser->getDoubleSidedSet(&doubleSideSet); + mMaterialParser->getDoubleSidedSet(&doubleSideSet); if (doubleSideSet) { doubleSidedCapability = true; rasterState.culling = doubleSided ? CullingMode::NONE : cullingMode; @@ -236,13 +236,13 @@ void MaterialDefinition::processMain() { } // specular anti-aliasing - materialParser->hasSpecularAntiAliasing(&specularAntiAliasing); + mMaterialParser->hasSpecularAntiAliasing(&specularAntiAliasing); if (specularAntiAliasing) { - materialParser->getSpecularAntiAliasingVariance(&specularAntiAliasingVariance); - materialParser->getSpecularAntiAliasingThreshold(&specularAntiAliasingThreshold); + mMaterialParser->getSpecularAntiAliasingVariance(&specularAntiAliasingVariance); + mMaterialParser->getSpecularAntiAliasingThreshold(&specularAntiAliasingThreshold); } - materialParser->hasCustomDepthShader(&hasCustomDepthShader); + mMaterialParser->hasCustomDepthShader(&hasCustomDepthShader); bool const isLit = isVariantLit || hasShadowMultiplier; bool const isSSR = reflectionMode == ReflectionMode::SCREEN_SPACE || @@ -253,14 +253,14 @@ void MaterialDefinition::processMain() { } void MaterialDefinition::processBlendingMode() { - materialParser->getBlendingMode(&blendingMode); + mMaterialParser->getBlendingMode(&blendingMode); if (blendingMode == BlendingMode::MASKED) { - materialParser->getMaskThreshold(&maskThreshold); + mMaterialParser->getMaskThreshold(&maskThreshold); } if (blendingMode == BlendingMode::CUSTOM) { - materialParser->getCustomBlendFunction(&customBlendFunctions); + mMaterialParser->getCustomBlendFunction(&customBlendFunctions); } // blending mode @@ -315,19 +315,19 @@ void MaterialDefinition::processBlendingMode() { // depth write bool depthWriteSet = false; - materialParser->getDepthWriteSet(&depthWriteSet); + mMaterialParser->getDepthWriteSet(&depthWriteSet); if (depthWriteSet) { bool depthWrite = false; - materialParser->getDepthWrite(&depthWrite); + mMaterialParser->getDepthWrite(&depthWrite); rasterState.depthWrite = depthWrite; } // alpha to coverage bool alphaToCoverageSet = false; - materialParser->getAlphaToCoverageSet(&alphaToCoverageSet); + mMaterialParser->getAlphaToCoverageSet(&alphaToCoverageSet); if (alphaToCoverageSet) { bool alphaToCoverage = false; - materialParser->getAlphaToCoverage(&alphaToCoverage); + mMaterialParser->getAlphaToCoverage(&alphaToCoverage); rasterState.alphaToCoverage = alphaToCoverage; } else { rasterState.alphaToCoverage = blendingMode == BlendingMode::MASKED; @@ -336,7 +336,7 @@ void MaterialDefinition::processBlendingMode() { void MaterialDefinition::processSpecializationConstants() { // Older materials won't have a constants chunk, but that's okay. - materialParser->getConstants(&materialConstants); + mMaterialParser->getConstants(&materialConstants); for (size_t i = 0, c = materialConstants.size(); i < c; i++) { auto& item = materialConstants[i]; // the key can be a string_view because mMaterialConstant owns the CString @@ -348,11 +348,11 @@ void MaterialDefinition::processSpecializationConstants() { void MaterialDefinition::processDescriptorSets(FEngine& engine) { UTILS_UNUSED_IN_RELEASE bool success; - success = materialParser->getDescriptorBindings(&programDescriptorBindings); + success = mMaterialParser->getDescriptorBindings(&programDescriptorBindings); assert_invariant(success); backend::DescriptorSetLayout descriptorSetLayout; - success = materialParser->getDescriptorSetLayout(&descriptorSetLayout); + success = mMaterialParser->getDescriptorSetLayout(&descriptorSetLayout); assert_invariant(success); // get the PER_VIEW descriptor binding info diff --git a/filament/src/MaterialDefinition.h b/filament/src/MaterialDefinition.h index 7cf77b3e6559..5f9a65f18a51 100644 --- a/filament/src/MaterialDefinition.h +++ b/filament/src/MaterialDefinition.h @@ -63,6 +63,8 @@ struct MaterialDefinition { // Free GPU resources owned by this MaterialDefinition. void terminate(FEngine& engine); + MaterialParser const& getMaterialParser() const noexcept { return *mMaterialParser; } + // try to order by frequency of use DescriptorSetLayout perViewDescriptorSetLayout; DescriptorSetLayout perViewDescriptorSetLayoutVsm; @@ -108,18 +110,19 @@ struct MaterialDefinition { // Constants defined by this Material utils::FixedCapacityVector materialConstants; - // A map from the Constant name to the mMaterialConstant index + // A map from the Constant name to the materialConstants index std::unordered_map specializationConstantsNameToIndex; utils::CString name; uint64_t cacheId = 0; - std::unique_ptr materialParser; private: void processMain(); void processBlendingMode(); void processSpecializationConstants(); void processDescriptorSets(FEngine& engine); + + std::unique_ptr mMaterialParser; }; } // namespace filament diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index 0e593c71a977..f06c8399328f 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -213,7 +213,7 @@ void FMaterial::terminate(FEngine& engine) { #endif destroyPrograms(engine); - engine.getMaterialCache().release(engine, *mDefinition.materialParser); + engine.getMaterialCache().release(engine, mDefinition.getMaterialParser()); } filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout( @@ -315,7 +315,7 @@ MaterialParser const& FMaterial::getMaterialParser() const noexcept { return *mEditedMaterialParser; } #endif - return *mDefinition.materialParser; + return mDefinition.getMaterialParser(); } bool FMaterial::hasVariant(Variant const variant) const noexcept { @@ -333,10 +333,10 @@ bool FMaterial::hasVariant(Variant const variant) const noexcept { return false; } const ShaderModel sm = mEngine.getShaderModel(); - if (!mDefinition.materialParser->hasShader(sm, vertexVariant, ShaderStage::VERTEX)) { + if (!mDefinition.getMaterialParser().hasShader(sm, vertexVariant, ShaderStage::VERTEX)) { return false; } - if (!mDefinition.materialParser->hasShader(sm, fragmentVariant, ShaderStage::FRAGMENT)) { + if (!mDefinition.getMaterialParser().hasShader(sm, fragmentVariant, ShaderStage::FRAGMENT)) { return false; } return true; @@ -798,7 +798,7 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Builder const& b +ReservedSpecializationConstants::CONFIG_SHADOW_SAMPLING_METHOD, int32_t(builder->mShadowSamplingQuality) }); if (UTILS_UNLIKELY( - mDefinition.materialParser->getShaderLanguage() == ShaderLanguage::ESSL1)) { + mDefinition.getMaterialParser().getShaderLanguage() == ShaderLanguage::ESSL1)) { // The actual value of this spec-constant is set in the OpenGLDriver backend. mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_SRGB_SWAPCHAIN_EMULATION, @@ -848,7 +848,7 @@ void FMaterial::processPushConstants(FEngine&) { CString structVarName; FixedCapacityVector pushConstants; - mDefinition.materialParser->getPushConstants(&structVarName, &pushConstants); + mDefinition.getMaterialParser().getPushConstants(&structVarName, &pushConstants); vertexConstants.reserve(pushConstants.size()); fragmentConstants.reserve(pushConstants.size()); From d6cd931908e355470118325af41c6a41a254d107 Mon Sep 17 00:00:00 2001 From: Eliza Velasquez Date: Wed, 17 Sep 2025 16:53:40 -0700 Subject: [PATCH 4/7] move RefCountedMap to utils and add unit tests --- filament/CMakeLists.txt | 1 - filament/src/MaterialCache.h | 4 +- libs/utils/CMakeLists.txt | 2 + .../utils/include/utils}/RefCountedMap.h | 33 ++- libs/utils/test/test_RefCountedMap.cpp | 222 ++++++++++++++++++ 5 files changed, 251 insertions(+), 11 deletions(-) rename {filament/src => libs/utils/include/utils}/RefCountedMap.h (89%) create mode 100644 libs/utils/test/test_RefCountedMap.cpp diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index be46022f7aaa..43c10bc13d7a 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -183,7 +183,6 @@ set(PRIVATE_HDRS src/MaterialInstanceManager.h src/PIDController.h src/PostProcessManager.h - src/RefCountedMap.h src/RenderPass.h src/RenderPrimitive.h src/RendererUtils.h diff --git a/filament/src/MaterialCache.h b/filament/src/MaterialCache.h index a265914d0a01..4c5e0b7639da 100644 --- a/filament/src/MaterialCache.h +++ b/filament/src/MaterialCache.h @@ -16,7 +16,6 @@ #ifndef TNT_FILAMENT_MATERIALCACHE_H #define TNT_FILAMENT_MATERIALCACHE_H -#include "RefCountedMap.h" #include "MaterialDefinition.h" #include @@ -27,6 +26,7 @@ #include #include +#include namespace filament { @@ -60,7 +60,7 @@ class MaterialCache { // // We use unique_ptr here because we need these pointers to be stable. // TODO: investigate using a custom allocator here? - RefCountedMap, Key::Hash> mInner; + utils::RefCountedMap, Key::Hash> mInner; }; } // namespace filament diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index dac610cc0f7e..5d71f5759032 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -36,6 +36,7 @@ set(DIST_HDRS ${PUBLIC_HDR_DIR}/${TARGET}/Path.h ${PUBLIC_HDR_DIR}/${TARGET}/PrivateImplementation.h ${PUBLIC_HDR_DIR}/${TARGET}/PrivateImplementation-impl.h + ${PUBLIC_HDR_DIR}/${TARGET}/RefCountedMap.h ${PUBLIC_HDR_DIR}/${TARGET}/SingleInstanceComponentManager.h ${PUBLIC_HDR_DIR}/${TARGET}/Slice.h ${PUBLIC_HDR_DIR}/${TARGET}/StaticString.h @@ -171,6 +172,7 @@ set(TEST_SRCS test/test_JobSystem.cpp test/test_QuadTreeArray.cpp test/test_RangeMap.cpp + test/test_RefCountedMap.cpp test/test_StructureOfArrays.cpp test/test_sstream.cpp test/test_string.cpp diff --git a/filament/src/RefCountedMap.h b/libs/utils/include/utils/RefCountedMap.h similarity index 89% rename from filament/src/RefCountedMap.h rename to libs/utils/include/utils/RefCountedMap.h index 5d7b1df35750..bc5f7ab57159 100644 --- a/filament/src/RefCountedMap.h +++ b/libs/utils/include/utils/RefCountedMap.h @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef TNT_FILAMENT_REFCOUNTEDMAP_H -#define TNT_FILAMENT_REFCOUNTEDMAP_H +#ifndef TNT_UTILS_REFCOUNTEDMAP_H +#define TNT_UTILS_REFCOUNTEDMAP_H #include #include @@ -22,13 +22,24 @@ #include -namespace filament { +namespace utils { namespace refcountedmap { template concept IsPointer = requires(T a) { *a; }; +// If T is a pointer type, get the type of the value it points to; otherwise, T. +template +struct PointerTraits { + using element_type = T; +}; + +template requires IsPointer +struct PointerTraits { + using element_type = std::pointer_traits::element_type; +}; + } // namespace refcountedmap /** A reference-counted map. @@ -40,9 +51,7 @@ template> class RefCountedMap { // Use references for the key if the size of the key type is greater than the size of a pointer. using KeyRef = std::conditional_t<(sizeof(Key) > sizeof(void*)), const Key&, Key>; - // If T is a pointer type, get the type of the value it points to; otherwise, T. - using TValue = typename std::conditional_t, - typename std::pointer_traits::element_type, T>; + using TValue = refcountedmap::PointerTraits::element_type; struct Entry { uint32_t referenceCount; @@ -59,6 +68,14 @@ class RefCountedMap { } } + static constexpr TValue const& deref(T const& a) { + if constexpr (refcountedmap::IsPointer) { + return *a; + } else { + return a; + } + } + static constexpr const char* UTILS_NONNULL MISSING_ENTRY_ERROR_STRING = "Cache is missing entry"; @@ -163,7 +180,7 @@ class RefCountedMap { * * Panics if no entry found in map. */ - T& get(KeyRef key, size_t hash) noexcept { + TValue& get(KeyRef key, size_t hash) noexcept { auto it = mMap.find(key); FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; return deref(it.value().value); @@ -188,4 +205,4 @@ class RefCountedMap { } -#endif // TNT_FILAMENT_REFCOUNTEDMAP_H +#endif // TNT_UTILS_REFCOUNTEDMAP_H diff --git a/libs/utils/test/test_RefCountedMap.cpp b/libs/utils/test/test_RefCountedMap.cpp new file mode 100644 index 000000000000..535228ca22dd --- /dev/null +++ b/libs/utils/test/test_RefCountedMap.cpp @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +using namespace utils; + +using KeyType = size_t; +using ValueType = size_t; +using PlainPointerType = size_t*; +using SmartPointerType = std::unique_ptr; + +/* Value types */ + +TEST(RefCountedMapTest, ValueType_AcquireAndRelease) { + RefCountedMap map; + + ValueType* a1 = map.acquire(1, []() { return 1; }); + EXPECT_NE(a1, nullptr); + EXPECT_EQ(*a1, 1); + EXPECT_FALSE(map.empty()); + + map.release(1); + EXPECT_TRUE(map.empty()); +} + +TEST(RefCountedMapTest, ValueType_AcquireAndReleaseMany) { + RefCountedMap map; + + ValueType* a1 = map.acquire(1, []() { return 1; }); + EXPECT_NE(a1, nullptr); + EXPECT_EQ(*a1, 1); + ValueType* a2 = map.acquire(1, []() { + ADD_FAILURE(); + return 1; + }); + EXPECT_EQ(a1, a2); + ValueType* a3 = map.acquire(1, []() { + ADD_FAILURE(); + return 1; + }); + EXPECT_EQ(a1, a3); + EXPECT_FALSE(map.empty()); + + map.release(1, [](ValueType& it) { ADD_FAILURE(); }); + EXPECT_FALSE(map.empty()); + map.release(1, [](ValueType& it) { ADD_FAILURE(); }); + EXPECT_FALSE(map.empty()); + map.release(1); + EXPECT_TRUE(map.empty()); +} + +TEST(RefCountedMapTest, ValueType_GetsValue) { + RefCountedMap map; + RefCountedMap const& constMap = map; + + map.acquire(1, []() { return 1; }); + ValueType& v1 = map.get(1); + EXPECT_EQ(v1, 1); + + ValueType const& v1const = constMap.get(1); + EXPECT_EQ(v1const, 1); +} + +TEST(RefCountedMapTest, ValueType_PanicsIfReleaseMissing) { + RefCountedMap map; + ASSERT_DEATH(map.release(1), ""); +} + +TEST(RefCountedMapTest, ValueType_PanicsIfGetsMissing) { + RefCountedMap map; + ASSERT_DEATH(map.get(1), ""); +} + +/* Plain pointer types */ + +TEST(RefCountedMapTest, PlainPointerType_AcquireAndRelease) { + RefCountedMap map; + + ValueType* a1 = map.acquire(1, []() { return new size_t(1); }); + EXPECT_NE(a1, nullptr); + EXPECT_EQ(*a1, 1); + EXPECT_FALSE(map.empty()); + + map.release(1, [](ValueType& it) { delete ⁢ }); + EXPECT_TRUE(map.empty()); +} + +TEST(RefCountedMapTest, PlainPointerType_AcquireAndReleaseMany) { + RefCountedMap map; + + ValueType* a1 = map.acquire(1, []() { return new size_t(1); }); + EXPECT_NE(a1, nullptr); + EXPECT_EQ(*a1, 1); + ValueType* a2 = map.acquire(1, []() { + ADD_FAILURE(); + return new size_t(1); + }); + EXPECT_EQ(a1, a2); + ValueType* a3 = map.acquire(1, []() { + ADD_FAILURE(); + return new size_t(1); + }); + EXPECT_EQ(a1, a3); + EXPECT_FALSE(map.empty()); + + map.release(1, [](ValueType& it) { + ADD_FAILURE(); + delete ⁢ + }); + EXPECT_FALSE(map.empty()); + map.release(1, [](ValueType& it) { + ADD_FAILURE(); + delete ⁢ + }); + EXPECT_FALSE(map.empty()); + map.release(1, [](ValueType& it) { delete ⁢ }); + EXPECT_TRUE(map.empty()); +} + +TEST(RefCountedMapTest, PlainPointerType_GetsValue) { + RefCountedMap map; + RefCountedMap const& constMap = map; + + ValueType* a1 = map.acquire(1, []() { return new size_t(1); }); + ValueType& v1 = map.get(1); + EXPECT_EQ(v1, 1); + + ValueType const& v1const = constMap.get(1); + EXPECT_EQ(v1const, 1); + + delete a1; +} + +TEST(RefCountedMapTest, PlainPointerType_PanicsIfReleaseMissing) { + RefCountedMap map; + ASSERT_DEATH(map.release(1), ""); +} + +TEST(RefCountedMapTest, PlainPointerType_PanicsIfGetsMissing) { + RefCountedMap map; + ASSERT_DEATH(map.get(1), ""); +} + + +/* Smart pointer types */ + +TEST(RefCountedMapTest, SmartPointerType_AcquireAndRelease) { + RefCountedMap map; + + ValueType* a1 = map.acquire(1, []() { return std::make_unique(1); }); + EXPECT_NE(a1, nullptr); + EXPECT_EQ(*a1, 1); + EXPECT_FALSE(map.empty()); + + map.release(1); + EXPECT_TRUE(map.empty()); +} + +TEST(RefCountedMapTest, SmartPointerType_AcquireAndReleaseMany) { + RefCountedMap map; + + ValueType* a1 = map.acquire(1, []() { return std::make_unique(1); }); + EXPECT_NE(a1, nullptr); + EXPECT_EQ(*a1, 1); + ValueType* a2 = map.acquire(1, []() { + ADD_FAILURE(); + return std::make_unique(1); + }); + EXPECT_EQ(a1, a2); + ValueType* a3 = map.acquire(1, []() { + ADD_FAILURE(); + return std::make_unique(1); + }); + EXPECT_EQ(a1, a3); + EXPECT_FALSE(map.empty()); + + map.release(1); + EXPECT_FALSE(map.empty()); + map.release(1); + EXPECT_FALSE(map.empty()); + map.release(1); + EXPECT_TRUE(map.empty()); +} + +TEST(RefCountedMapTest, SmartPointerType_GetsValue) { + RefCountedMap map; + RefCountedMap const& constMap = map; + + ValueType* a1 = map.acquire(1, []() { return std::make_unique(1); }); + + ValueType& v1 = map.get(1); + EXPECT_EQ(v1, 1); + + ValueType const& v1const = constMap.get(1); + EXPECT_EQ(v1const, 1); +} + +TEST(RefCountedMapTest, SmartPointerType_PanicsIfReleaseMissing) { + RefCountedMap map; + ASSERT_DEATH(map.release(1), ""); +} + +TEST(RefCountedMapTest, SmartPointerType_PanicsIfGetsMissing) { + RefCountedMap map; + ASSERT_DEATH(map.get(1), ""); +} From f09fe898ea5cc7bb442b6e349555e2dbee18406c Mon Sep 17 00:00:00 2001 From: Eliza Velasquez Date: Wed, 17 Sep 2025 16:57:03 -0700 Subject: [PATCH 5/7] material cache: make create functions private --- filament/src/MaterialDefinition.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/filament/src/MaterialDefinition.h b/filament/src/MaterialDefinition.h index 5f9a65f18a51..47ad39caaf7a 100644 --- a/filament/src/MaterialDefinition.h +++ b/filament/src/MaterialDefinition.h @@ -48,15 +48,6 @@ struct MaterialDefinition { using BindingUniformInfoContainer = utils::FixedCapacityVector< std::tuple>; - // Called by MaterialCache. - static std::unique_ptr createParser(backend::Backend const backend, - utils::FixedCapacityVector languages, - const void* UTILS_NONNULL data, size_t size); - - // Called by MaterialCache. - static std::unique_ptr create(FEngine& engine, - std::unique_ptr parser); - // public only due to std::make_unique(). MaterialDefinition(FEngine& engine, std::unique_ptr parser); @@ -117,6 +108,15 @@ struct MaterialDefinition { uint64_t cacheId = 0; private: + friend class MaterialCache; + + static std::unique_ptr createParser(backend::Backend const backend, + utils::FixedCapacityVector languages, + const void* UTILS_NONNULL data, size_t size); + + static std::unique_ptr create(FEngine& engine, + std::unique_ptr parser); + void processMain(); void processBlendingMode(); void processSpecializationConstants(); From 5ed999ed2b02ed9b6cf466fd5618656087a82378 Mon Sep 17 00:00:00 2001 From: Eliza Velasquez Date: Mon, 22 Sep 2025 14:13:36 -0700 Subject: [PATCH 6/7] material cache: fix broken tests on iOS/web --- libs/utils/test/test_RefCountedMap.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/utils/test/test_RefCountedMap.cpp b/libs/utils/test/test_RefCountedMap.cpp index 535228ca22dd..f4e0cff65a58 100644 --- a/libs/utils/test/test_RefCountedMap.cpp +++ b/libs/utils/test/test_RefCountedMap.cpp @@ -77,6 +77,7 @@ TEST(RefCountedMapTest, ValueType_GetsValue) { EXPECT_EQ(v1const, 1); } +#ifdef GTEST_HAS_DEATH_TEST TEST(RefCountedMapTest, ValueType_PanicsIfReleaseMissing) { RefCountedMap map; ASSERT_DEATH(map.release(1), ""); @@ -86,6 +87,7 @@ TEST(RefCountedMapTest, ValueType_PanicsIfGetsMissing) { RefCountedMap map; ASSERT_DEATH(map.get(1), ""); } +#endif // GTEST_HAS_DEATH_TEST /* Plain pointer types */ @@ -147,6 +149,7 @@ TEST(RefCountedMapTest, PlainPointerType_GetsValue) { delete a1; } +#ifdef GTEST_HAS_DEATH_TEST TEST(RefCountedMapTest, PlainPointerType_PanicsIfReleaseMissing) { RefCountedMap map; ASSERT_DEATH(map.release(1), ""); @@ -156,7 +159,7 @@ TEST(RefCountedMapTest, PlainPointerType_PanicsIfGetsMissing) { RefCountedMap map; ASSERT_DEATH(map.get(1), ""); } - +#endif // GTEST_HAS_DEATH_TEST /* Smart pointer types */ @@ -211,6 +214,7 @@ TEST(RefCountedMapTest, SmartPointerType_GetsValue) { EXPECT_EQ(v1const, 1); } +#ifdef GTEST_HAS_DEATH_TEST TEST(RefCountedMapTest, SmartPointerType_PanicsIfReleaseMissing) { RefCountedMap map; ASSERT_DEATH(map.release(1), ""); @@ -220,3 +224,4 @@ TEST(RefCountedMapTest, SmartPointerType_PanicsIfGetsMissing) { RefCountedMap map; ASSERT_DEATH(map.get(1), ""); } +#endif // GTEST_HAS_DEATH_TEST From 28c10314d9673bdd9bfb4fc2a4a21947c40a4897 Mon Sep 17 00:00:00 2001 From: Eliza Velasquez Date: Tue, 23 Sep 2025 14:26:58 -0700 Subject: [PATCH 7/7] material cache: address more comments --- .../backend/include/backend/DriverEnums.h | 9 +++++++ filament/src/MaterialCache.cpp | 25 +++++++++---------- filament/src/MaterialCache.h | 5 +--- filament/src/MaterialDefinition.cpp | 17 +++---------- 4 files changed, 25 insertions(+), 31 deletions(-) diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index 153a932780ba..0838230f2526 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -636,6 +636,15 @@ enum class ShaderModel : uint8_t { }; static constexpr size_t SHADER_MODEL_COUNT = 2; +constexpr std::string_view to_string(ShaderModel model) noexcept { + switch (model) { + case ShaderModel::MOBILE: + return "mobile"; + case ShaderModel::DESKTOP: + return "desktop"; + } +} + /** * Primitive types */ diff --git a/filament/src/MaterialCache.cpp b/filament/src/MaterialCache.cpp index f260bc359429..325b2cfc243e 100644 --- a/filament/src/MaterialCache.cpp +++ b/filament/src/MaterialCache.cpp @@ -26,12 +26,13 @@ namespace filament { -size_t MaterialCache::Key::Hash::operator()(filament::MaterialCache::Key const& x) const noexcept { - uint32_t r; - if (x.parser->getMaterialCrc32(&r)) { - return size_t(r); +size_t MaterialCache::Key::Hash::operator()( + filament::MaterialCache::Key const& key) const noexcept { + uint32_t crc; + if (key.parser->getMaterialCrc32(&crc)) { + return size_t(crc); } - return size_t(x.parser->computeCrc32()); + return size_t(key.parser->computeCrc32()); } bool MaterialCache::Key::operator==(Key const& rhs) const noexcept { @@ -39,26 +40,24 @@ bool MaterialCache::Key::operator==(Key const& rhs) const noexcept { } MaterialCache::~MaterialCache() { - if (!mInner.empty()) { + if (!mDefinitions.empty()) { LOG(WARNING) << "MaterialCache was destroyed but wasn't empty"; } } MaterialDefinition* UTILS_NULLABLE MaterialCache::acquire(FEngine& engine, const void* UTILS_NONNULL data, size_t size) noexcept { - std::unique_ptr parser = MaterialDefinition::createParser( - engine.getBackend(), engine.getShaderLanguage(), data, size); - if (!parser) { - return nullptr; - } + std::unique_ptr parser = MaterialDefinition::createParser(engine.getBackend(), + engine.getShaderLanguage(), data, size); + assert_invariant(parser); - return mInner.acquire(Key{parser.get()}, [&engine, parser = std::move(parser)]() mutable { + return mDefinitions.acquire(Key{parser.get()}, [&engine, parser = std::move(parser)]() mutable { return MaterialDefinition::create(engine, std::move(parser)); }); } void MaterialCache::release(FEngine& engine, MaterialParser const& parser) noexcept { - mInner.release(Key{&parser}, [&engine](MaterialDefinition& definition) { + mDefinitions.release(Key{&parser}, [&engine](MaterialDefinition& definition) { definition.terminate(engine); }); } diff --git a/filament/src/MaterialCache.h b/filament/src/MaterialCache.h index 4c5e0b7639da..6409b092f0c0 100644 --- a/filament/src/MaterialCache.h +++ b/filament/src/MaterialCache.h @@ -32,7 +32,6 @@ namespace filament { class Material; -/** A cache of all materials and shader programs compiled by Filament. */ class MaterialCache { // A newtype around a material parser used as a key for the material cache. The material file's // CRC32 is used as the hash function. @@ -56,11 +55,9 @@ class MaterialCache { void release(FEngine& engine, MaterialParser const& parser) noexcept; private: - // Program caches are keyed by the material UUID. - // // We use unique_ptr here because we need these pointers to be stable. // TODO: investigate using a custom allocator here? - utils::RefCountedMap, Key::Hash> mInner; + utils::RefCountedMap, Key::Hash> mDefinitions; }; } // namespace filament diff --git a/filament/src/MaterialDefinition.cpp b/filament/src/MaterialDefinition.cpp index 3d8582c68f5d..790e2d835fff 100644 --- a/filament/src/MaterialDefinition.cpp +++ b/filament/src/MaterialDefinition.cpp @@ -3,6 +3,8 @@ #include "Froxelizer.h" #include "MaterialParser.h" +#include + #include #include
@@ -15,19 +17,6 @@ namespace filament { using namespace backend; using namespace utils; -namespace { - -static const char* toString(ShaderModel model) { - switch (model) { - case ShaderModel::MOBILE: - return "mobile"; - case ShaderModel::DESKTOP: - return "desktop"; - } -} - -} // namespace - std::unique_ptr MaterialDefinition::createParser(Backend const backend, FixedCapacityVector languages, const void* data, size_t size) { // unique_ptr so we don't leak MaterialParser on failures below @@ -99,7 +88,7 @@ std::unique_ptr MaterialDefinition::create(FEngine& engine, char shaderModelsString[16]; snprintf(shaderModelsString, sizeof(shaderModelsString), "%#x", shaderModels.getValue()); LOG(ERROR) << "The material '" << name.c_str_safe() << "' was not built for " - << toString(shaderModel) << "."; + << to_string(shaderModel) << "."; LOG(ERROR) << "Compiled material contains shader models " << shaderModelsString << "."; return nullptr; }