* engine: add program cache
This is another chunky change.
The core of this change is to cache programs in MaterialCache according to a
"specialization" (ProgramSpecialization) which is defined as the program cache
ID (the same key used for the OpenGL binary blob cache), the variant, and the
set of spec constants.
As part of this change, a lot of the implementation details of shader
compilation were refactored from Material to MaterialDefinition. The resulting
flow is a lot cleaner and easier to reason about, since shader compilation is
now a pure function of the MaterialDefinition + ProgramSpecialization.
Since the global cache program lookups might take a bit of time to compute
hashes, etc, I left the set of cached programs in Material as well, which kind
of acts like an L1 cache. The effect is that prepareProgram() and getProgram()
should be no slower than HEAD, even with the more complex caching requirements.
I'm planning on writing a document about this (and all changes up until this
point), but I'm being asked to work on higher priority things and I wanted to
have this PR out for review in the meantime so it doesn't bitrot.
* engine: fix unit tests
* engine: fix spec constants intern pool memory leak
* engine: address program cache comments
* engine: address more program cache comments
* engine: matdbg support for program cache
* engine: reinstate descriptorLayout calls
* engine: address bitrot
* engine: add feature flag to disable program cache
* engine: use material CRC32 for program cache
The "cache ID" of a material is supposed to uniquely identify a shader program
and all its variants. This is true to a certain extent, but does not account for
the code generation that happens at runtime. Two materials may have "identical"
shader programs, but due to each material's differing unique metadata, the final
compiled programs may end up very different. Unfortunately, this means we cannot
rely on the "cache ID" alone to determine a shader program's reusability.
Ideally, we should hash this "cache ID" with the exact set of changes to each
shader program so that we could reuse programs across materials. Instead, as a
stopgap solution, use the material's CRC32 instead.
* engine: fix double-free in program cache
* engine: address comments
* engine: assert_invariant empty material cache
The "const char*" used for the string literal parameter of the `feature`
method becomes invalid once it passes outside the Java binding scope,
leading to invalid data access. This fix ensures the string is safely
stored.
Add support for heterogeneous lookup in associative containers that
use CString keys. Add conversion operators for std::string_view and
raw string literals to CString.
* Use a custom, non-allocating map for frame ids
* use memory_order_relaxed for the id of heap allocated handles
* Added displayPresent time to FrameInfo
To do this, we added support for queryFrameTiming() to the backend,
as a synchronous API. Then FrameInfo uses it to update the
corresponding history entry.
displayPresent time can be used to detect "buffer stuffing", i.e.
when the GPU gets too much ahead of the display. Currently
the information is returned to the user only. Eventually filament will
make use of it to determine if a frame skip is mandated.
* API BREAK: rename frameTime to gpuFrameDuration
* FrameInfo now contains compositor timings
- the presentation deadline
- the refresh rate from the display
- the composition-display latency
* set FrameInfo data to INVALID if feature is not supported
report presentDeadline properly.
* Make JsonishParser use utils::Status
- add unsupported error in utils::Status
- add invalid case in the test
* return a pair of Status and string in resolveEscapes;
use initializer list for JsonishString and move that to the header.
* Use utils::Status in MaterialParser
* Use utils::sstream instead of std::stringstream
* Remove remaining std::cerr and dep; update MaterialParser::reflectParameters
* make error message in utils::Status more generic
---------
Co-authored-by: Powei Feng <powei@google.com>
ImmutableCString is a string class similar to CString except it's
immutable. ImmutableCString occupies 16 bytes instead of 8 for CString.
However, ImmutableCString is able to avoid memory allocation when
constructed from a string literal, and in that way it us similar
to StaticString.
ImmutableCString can be auto converted from StaticString.
The backend tag tracking is updated to use ImmutableCString and
the FrameGraph resource manager us updated to use StaticString.
Together these changes significantly cut down heap allocations due to
internal tagging.
We also add optional tracking to {Immutable}CString.
* utils: RefCountedInternPool/RefCountedMap
First, introduce RefCountedInternPool, a reference counted intern pool of
Slice<const T>. Just acquire() a slice that you want and you're guaranteed to
get exactly one canonical value-equal Slice<const T> back.
Additionally, introduce the concept of NullValue to RefCountedMap. A NullValue
defines what should be considered an uninitialized value; by default, it's the
default value of that type (0 for ints, nullptr for pointers, etc). This allows
us to lazily-initialize values in the map. A client can acquire() a bunch of
different resources which will be initialized only when get(factory) is called.
If a client attempts to get() a value without specifying a factory, and the
value is not initialized (i.e. equal to NullValue{}()), RefCountedMap will
panic.
* utils: add unit tests for ref-counted collections
* utils: remove C++20 features, fix memory issue
* utils: remove RefCounted from InternPool
* slice: fix memory semantics
* slice: prefer passing slice by value
This lets us do nice things like coercing Slice<T> to Slice<const T>, etc.
* slice: fix unit tests
* slice: fix copy/assignment, hash function
Don't attempt to define a copy constructor/assignment operator which would
convert a constant type to a mutable type.
Additionally, fix the hash function such that we're hashing U instead of const
U.
* 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.
* material cache: key on crc32
* material cache: make materialParser private
* move RefCountedMap to utils and add unit tests
* material cache: make create functions private
* material cache: fix broken tests on iOS/web
* material cache: address more comments
- libbackend (except webgpu)
- libfilament
- libutils
std::string generates a lot of code bloat, we use CString instead.
We also update CString to be more compatible with std::string's api.
This commit introduces a CRC32 checksum to material packages to ensure
data integrity.
When a material is loaded, this checksum is verified. If the check
fails, an error is logged, and the material fails to load. For older
material packages without a CRC32 checksum, a warning is logged and
proceed.
BUGS=[373396840]
This 2D allocator only supports power-of-two, square allocations. It
uses a quadtree to store the allocated regions.
The quadtree implementation currently has a fixed depth (templated).
The allocator does a best-fit match, but currently doesn't implement a
free method.
In most places this is simply replaced by `std::string_view`.
We also change a few internal/private headers so they accept
`std::string_view` instead of `utils::CString`.
There were two places where we were doing unaligned reads: one when
computing the hash for the material identifier, and one when parsing
the chunk in ShaderReplacer.
We also had a potential overflow since civetweb does not add a trailing
null to incoming WebSockets messages.
The existing unit test had an incorrect expectation in what happens
after splicing an existing range. Fixing the unit test revealed an
actual bug in the RangeMap implementation. It should never modify the
lower bound of an existing interval, because that invalidates the
iterator. It should also never retain a pointer or reference to a range
when the range is potentially removed by subsequent codelines.
This will allow the Vulkan backend to efficiently track the subresource
image layouts for each texture.
This is a sparse container for a series of ordered non-overlapping
integer intervals, where each interval maps to a concrete value.
* Use locale-independent string->float conversion
strtof and friends are locale aware and won't parse decimal numbers
with a period ("12.6" for instance) in locales that use another
character for the decimal period ("," in French for instance).
This change introduces a new function called strtof_c that forces
the use of a specific locale (called "C") to make sure we always
parse floats in the desired "C" format ("12.6").
With C++17 we should be able to use std::from_chars but this API
is not implemented in clang for floats at the moment.
* Fix Linux
A side effect of this flag was that we had to always use notify_all
when starting a job, instead of notify_one -- in practice, we found
experimentally that more calls to notify_one is cheaper than fewer
calls to notify_all.
TrackingPolicy::Debug didn't store the base pointer of the Area, and
instead relied on the first allocation to discover it, however, because
of alignment, the first allocation may not match the base pointer.
Because of that there could be an overflow in onRewind(), i.e. we
could rewind to a pointer before the (wrongly computed) base. This
overflow caused the debug memset to go awry and stomped on memory.
This is fixed by passing the base pointer to the constructor of the
TrackingPolicy. This base pointer could be nullptr with certain
allocators, but in that case, onReset/onRewind should never be called;
and this is enforced at compile time.
Also fixed a (luckily) harmless buffer overflow when preparing the
dynamic lights, if the number of lights wasn't a multiple of 4. This
was harmless because we use a linear allocator, so overflows are not
really overflows.
This was caught by ASAN.
Our algorithm header has many one-liners that compute the "next power of
two length / 2" but they all have the caveat that if the input is
already POT, then the "/ 2" part does not occur.
Usually we deal with this by testing the difference against zero.
However in `partition_point` we were skipping the test, thus causing a
potential out-of-bounds access.
I fixed `partition_point` and added a few more tests for non-POT cases.
* Add #include preprocessing to filamat and matc
* Update RELEASE_NOTES
* Fix RELEASE_NOTES
* Use final instead of virtual / override
* Clarify comments
* Use pure virtual for includer functions
* Use a callback instead of an interface
* Rename Includer.h to IncludeCallback.h
* Update comment
The Work-stealing dequeue indices could wrap around after ~2 billion
calls to steal(). This could probably be achieved in a few hours.
By using 64-bits indices, we avoid the problem entirely.