The main idea is to make use of socket modified flags to detect what
exactly has changed in the scene to avoid updating some data
unnecessarily.
One thing I tried to achieve is to make the tag_update methods on Nodes
(e.g. Background::tag_update) do some detection and tag appropriate
managers or nodes for updates only if necessary, so update tagging in
the system will depend entirely on the Nodes' modified sockets. For API
clients, this might reduce the need to manually tag managers for
updates, as one would simply update a Node and call its tag_update
method (which could become a virtual function on the Node base class to
make it clearer, and managers could be kept away from the public API).
To complement this, the managers tag_update methods now also take a flag
parameter to tell why the tagging is done, so we can eventually make
better decisions. Those flags replace the single need_update boolean
that managers have, and are private. We can access the state of those
flags via a public bool need_update() method. The set of flags is
global, i.e. there is only one enumeration for all the managers. I tried
having specific enumerations for each manager, but that led to
duplicated flags, was a bit tricky to maintain and weird to work with.
Similar flags are used in the Object and Geometry managers to decide if
the device arrays are to be simply updated (data copied in place) or
reallocated. This can be cleaned up a bit.
I will update my benchmarks and post some results a bit later as there
were some improvements since the time they were first shared.
Here is a summary of all the changes, if I did not forget anything:
OptiX:
- multithreaded BVH packing, and added some logic to not repack the data
from unchanged geometries
- skip rebuilding the BVH for unmodified objects
- added refit support for the scene BVH, this requires storing the BVH
in the GeometryManager, instead of recreating it every time, so we can
keep the OptiX handles around
GeometryManager:
- skip repacking data in the various device vectors for unmodified
geometries or attributes
- do not rebuild the scene BVH if nothing changed
- moved some shader change detection in device_update_preprocess, was in
device_update
Attribute:
- added some logic to detect if an attribute has changed when updating
the attributes on a geometry, so we know which ones to repack, this
might be better implemented with attributes as sockets (I have a partial
implementation but needs a good design)
Shader:
- split need_update_geometry into 3 different flags for more
granularity: need_update_uvs (for UV pass), need_update_attributes, and
need_update_displacement; the first two ones will tag the entire
geometry as modified as we do not have yet attributes as sockets
Integrator:
- use modified flags to skip somewhat expensive sample_pattern_lut
rebuilds, and only tag the integrator dependent shaders if the
filter_glossy socket was modified (I think this is only socket affecting
those?)
- some unnecessary calls to Integrator::tag_update were removed,
perhaps one more can be removed in the passes updates as
Film::tag_update will tag for an Integrator update if the AO passes
change?
Background:
- only tag the Integrator as modified in Background::tag_update if the
AO related sockets have been modified
device_memory:
- added a modified flag to detect if we need to copy the memory to the
device
- added some logic to compute and report the amount of memory copied to
the device, this is a bit rough though and uses std::cerr, and might
be better to have as part of the UpdateStatistics, or some specialized
stats.
ObjectManager:
- avoid recomputing transforms for objects that were not modified
The Light, Shader, and Object managers were a bit tricky to optimize
because of all the implicit dependencies, so nothing much happened there
(IES light updates can be granularized though). Any tips welcome, maybe
there is some low hanging fruits or simple refactors to do.
