Skip to content

Conversation

elizagamedev
Copy link
Contributor

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.

@elizagamedev
Copy link
Contributor Author

Oops, just realized I forgot to do the messy work of getting this working with matdbg. I'll comment again when I get that working.

@pixelflinger pixelflinger added the internal Issue/PR does not affect clients label Sep 11, 2025
@elizagamedev elizagamedev force-pushed the exv/material-cache branch 2 times, most recently from 9e4833e to 1aeb8b2 Compare September 11, 2025 21:38
@elizagamedev
Copy link
Contributor Author

Okay, fixed matdbg.

@bejado
Copy link
Member

bejado commented Sep 12, 2025

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.

I've only just started reading through your implementation, so maybe I'm missing something obvious, but I think there's an issue here relying on a material's cache ID (parser->getCacheId()), which is perhaps poorly named. The cache ID only takes into account the vertex and fragment blocks and material version (see MaterialBuilder.cpp). So if, for example, specularAntiAliasing changes, the cache ID won't.

@elizagamedev
Copy link
Contributor Author

I've only just started reading through your implementation, so maybe I'm missing something obvious, but I think there's an issue here relying on a material's cache ID (parser->getCacheId()), which is perhaps poorly named. The cache ID only takes into account the vertex and fragment blocks and material version (see MaterialBuilder.cpp). So if, for example, specularAntiAliasing changes, the cache ID won't.

Hmm, in that case, maybe the CRC32 is better. I could also change the key of the hashmap to also do a direct memory comparison in the case of a collision.

@elizagamedev
Copy link
Contributor Author

Hmm, in that case, maybe the CRC32 is better. I could also change the key of the hashmap to also do a direct memory comparison in the case of a collision.

Done.

Copy link
Collaborator

@pixelflinger pixelflinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only minor naming comments

@pixelflinger
Copy link
Collaborator

@elizagamedev iOS and web are failing FYI.

@pixelflinger
Copy link
Collaborator

I've only just started reading through your implementation, so maybe I'm missing something obvious, but I think there's an issue here relying on a material's cache ID (parser->getCacheId()), which is perhaps poorly named. The cache ID only takes into account the vertex and fragment blocks and material version (see MaterialBuilder.cpp). So if, for example, specularAntiAliasing changes, the cache ID won't.

Hmm, in that case, maybe the CRC32 is better. I could also change the key of the hashmap to also do a direct memory comparison in the case of a collision.

So yes, "cache id" is indeed poorly named. It specifically only caches was goes into "Program". So it's not good for caching packages, however, it's good for caching programs (in the future). e.g. two packages with different blending modes, could have the same shaders (internally Program also mixes in the variant and the spec constants).

@elizagamedev elizagamedev enabled auto-merge (squash) September 24, 2025 00:11
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.
@elizagamedev elizagamedev merged commit 5516cd9 into main Sep 24, 2025
16 checks passed
@elizagamedev elizagamedev deleted the exv/material-cache branch September 24, 2025 00:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

internal Issue/PR does not affect clients

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants