tornavis/source/blender/depsgraph/intern/depsgraph_light_linking.cc

507 lines
15 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup depsgraph
*
* Light linking utilities. */
#include "intern/depsgraph_light_linking.hh"
#include "MEM_guardedalloc.h"
#include "BLI_hash.hh"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_utildefines.h"
#include "BKE_collection.hh"
#include "DNA_collection_types.h"
#include "DNA_layer_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DEG_depsgraph_light_linking.hh"
#include "DEG_depsgraph_query.hh"
#include "intern/depsgraph.hh"
namespace deg = blender::deg;
/* -------------------------------------------------------------------- */
/** \name Public C++ API
* \{ */
namespace blender::deg::light_linking {
void eval_runtime_data(const ::Depsgraph *depsgraph, Object &object_eval)
{
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(depsgraph);
deg_graph->light_linking_cache.eval_runtime_data(object_eval);
}
} // namespace blender::deg::light_linking
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal builder API
* \{ */
namespace {
/* TODO(sergey): Move to a public API, solving the const-correctness. */
template<class T> static inline const T *get_original(const T *id)
{
if (!id) {
return nullptr;
}
return reinterpret_cast<T *>(DEG_get_original_id(const_cast<ID *>(&id->id)));
}
/* Check whether the ID is suitable to be an input of the dependency graph. */
/* TODO(sergey): Move the function and check to a more generic place. */
#ifndef NDEBUG
bool is_valid_input_id(const ID &id)
{
return (id.tag & LIB_TAG_LOCALIZED) || DEG_is_original_id(&id);
}
#endif
} // namespace
namespace blender::deg::light_linking {
using LightSet = internal::LightSet;
using EmitterData = internal::EmitterData;
using EmitterDataMap = internal::EmitterDataMap;
using EmitterSetMembership = internal::EmitterSetMembership;
using LinkingData = internal::LinkingData;
namespace internal {
namespace {
/* Helper class which takes care of allocating an unique light set IDs, performing checks for
* overflows. */
class LightSetIDManager {
using LightSet = internal::LightSet;
public:
explicit LightSetIDManager(const Scene &scene) : scene_(scene) {}
bool get(const LightSet &light_set, uint64_t &id)
{
/* Performance note.
*
* Always ensure the light set data exists in the map, even when an overflow happens. This has
* a downside of potentially higher memory usage and when there are many emitters with light
* linking, but it avoids distinct lookup + add fore the normal cases. */
const uint64_t light_set_id = light_set_id_map_.lookup_or_add_cb(light_set, [&]() {
const uint64_t new_light_set_id = next_light_set_id_++;
if (new_light_set_id == LightSet::MAX_ID + 1) {
printf("Maximum number of light linking sets (%d) exceeded scene \"%s\".\n",
LightSet::MAX_ID + 1,
scene_.id.name + 2);
}
return new_light_set_id;
});
id = light_set_id;
return id <= LightSet::MAX_ID;
}
private:
const Scene &scene_;
/* Next unique ID of a light set. */
uint64_t next_light_set_id_ = LightSet::DEFAULT_ID + 1;
/* Map from a link set to its original id. */
Map<internal::LightSet, uint64_t> light_set_id_map_;
};
} // namespace
/* LightSet */
bool LightSet::operator==(const LightSet &other) const
{
return include_collection_mask == other.include_collection_mask &&
exclude_collection_mask == other.exclude_collection_mask;
}
uint64_t LightSet::hash() const
{
return get_default_hash(get_default_hash(include_collection_mask),
get_default_hash(exclude_collection_mask));
}
/* EmitterSetMembership */
uint64_t EmitterSetMembership::get_mask() const
{
const uint64_t effective_included_mask = included_sets_mask ? included_sets_mask :
SET_MEMBERSHIP_ALL;
return effective_included_mask & ~excluded_sets_mask;
}
/* EmitterDataMap. */
void EmitterDataMap::clear()
{
emitter_data_map_.clear();
next_collection_id_ = 0;
}
EmitterData *EmitterDataMap::ensure_data_if_possible(const Scene &scene, const Object &emitter)
{
BLI_assert(is_valid_input_id(emitter.id));
const Collection *collection = get_collection(emitter);
BLI_assert(collection);
/* Performance note.
*
* Always ensure the emitter data exists in the map, even when an overflow happens. This has a
* downside of potentially higher memory usage when there are many emitters with light linking,
* but it avoids distinct lookup + add fore the normal cases.
*
* On the API level the function always returns nullptr on overflow, so it is more of an internal
* behavior. */
EmitterData &emitter_data = emitter_data_map_.lookup_or_add_cb(collection, [&]() {
const uint64_t collection_id = next_collection_id_++;
EmitterData new_emitter_data;
if (collection_id > EmitterData::MAX_COLLECTION_ID) {
if (collection_id == EmitterData::MAX_COLLECTION_ID + 1) {
printf("Maximum number of light linking collections (%d) exceeded in scene \"%s\".\n",
EmitterData::MAX_COLLECTION_ID + 1,
scene.id.name + 2);
}
new_emitter_data.collection_mask = 0;
}
else {
new_emitter_data.collection_mask = uint64_t(1) << collection_id;
}
return new_emitter_data;
});
if (!emitter_data.collection_mask) {
return nullptr;
}
return &emitter_data;
}
const EmitterData *EmitterDataMap::get_data(const Object &emitter) const
{
const Collection *collection_eval = get_collection(emitter);
if (!collection_eval) {
return nullptr;
}
const Collection *collection_orig = get_original(collection_eval);
return emitter_data_map_.lookup_ptr(collection_orig);
}
bool EmitterDataMap::can_skip_emitter(const Object &emitter) const
{
BLI_assert(is_valid_input_id(emitter.id));
const Collection *collection = get_collection(emitter);
if (!collection) {
return true;
}
return emitter_data_map_.contains(collection);
}
/* LinkingData */
void LinkingData::clear()
{
light_linked_sets_.clear();
object_light_sets_.clear();
}
void LinkingData::link_object(const EmitterData &emitter_data,
const eCollectionLightLinkingState link_state,
const Object &object)
{
LightSet &light_set = ensure_light_set_for(object);
switch (link_state) {
case COLLECTION_LIGHT_LINKING_STATE_INCLUDE:
light_set.include_collection_mask |= emitter_data.collection_mask;
light_set.exclude_collection_mask &= ~emitter_data.collection_mask;
break;
case COLLECTION_LIGHT_LINKING_STATE_EXCLUDE:
light_set.exclude_collection_mask |= emitter_data.collection_mask;
light_set.include_collection_mask &= ~emitter_data.collection_mask;
break;
}
}
LightSet &LinkingData::ensure_light_set_for(const Object &object)
{
BLI_assert(is_valid_input_id(object.id));
return light_linked_sets_.lookup_or_add_as(&object);
}
void LinkingData::clear_after_build()
{
light_linked_sets_.clear_and_shrink();
}
void LinkingData::end_build(const Scene &scene, EmitterDataMap &emitter_data_map)
{
LightSetIDManager light_set_id_manager(scene);
for (const auto it : light_linked_sets_.items()) {
const Object *receiver = it.key;
LightSet &light_set = it.value;
uint64_t light_set_id;
if (!light_set_id_manager.get(light_set, light_set_id)) {
continue;
}
const uint64_t light_set_mask = uint64_t(1) << light_set_id;
object_light_sets_.add(receiver, light_set_id);
update_emitters_membership(emitter_data_map, light_set, light_set_mask);
}
clear_after_build();
}
void LinkingData::update_emitters_membership(EmitterDataMap &emitter_data_map,
const LightSet &light_set,
const uint64_t light_set_mask)
{
for (EmitterData &emitter_data : emitter_data_map.values()) {
EmitterSetMembership &set_membership = get_emitter_set_membership(emitter_data);
if (emitter_data.collection_mask & light_set.include_collection_mask) {
set_membership.included_sets_mask |= light_set_mask;
}
if (emitter_data.collection_mask & light_set.exclude_collection_mask) {
set_membership.excluded_sets_mask |= light_set_mask;
}
}
}
uint64_t LinkingData::get_light_set_for(const Object &object) const
{
const Object *object_orig = get_original(&object);
return object_light_sets_.lookup_default(object_orig, LightSet::DEFAULT_ID);
}
} // namespace internal
namespace {
/* Iterate over all objects of the collection and invoke the given callback with two arguments:
* the given collection light linking settings, and the object (passed as reference).
*
* Note that if an object is reachable from multiple children collection the callback is invoked
* for all of them. */
template<class Proc>
static void foreach_light_collection_object_inner(
const CollectionLightLinking &collection_light_linking,
const Collection &collection,
Proc &&callback)
{
LISTBASE_FOREACH (const CollectionChild *, collection_child, &collection.children) {
foreach_light_collection_object_inner(
collection_light_linking, *collection_child->collection, callback);
}
LISTBASE_FOREACH (const CollectionObject *, collection_object, &collection.gobject) {
callback(collection_light_linking, *collection_object->ob);
}
}
/* Iterate over all objects of the collection and invoke the given callback with two arguments:
* CollectionLightLinking and the actual Object (passed as reference).
*
* The CollectionLightLinking denotes the effective light linking settings of the object. It comes
* from the first level of hierarchy from the given collection.
*
* Note that if an object is reachable from multiple children collection the callback is invoked
* for all of them. */
template<class Proc>
static void foreach_light_collection_object(const Collection &collection, Proc &&callback)
{
LISTBASE_FOREACH (const CollectionChild *, collection_child, &collection.children) {
foreach_light_collection_object_inner(
collection_child->light_linking, *collection_child->collection, callback);
}
LISTBASE_FOREACH (const CollectionObject *, collection_object, &collection.gobject) {
callback(collection_object->light_linking, *collection_object->ob);
}
}
} // namespace
void Cache::clear()
{
light_emitter_data_map_.clear();
shadow_emitter_data_map_.clear();
light_linking_.clear();
shadow_linking_.clear();
}
void Cache::add_emitter(const Scene &scene, const Object &emitter)
{
BLI_assert(is_valid_input_id(emitter.id));
add_light_linking_emitter(scene, emitter);
add_shadow_linking_emitter(scene, emitter);
}
void Cache::add_light_linking_emitter(const Scene &scene, const Object &emitter)
{
BLI_assert(is_valid_input_id(emitter.id));
if (light_emitter_data_map_.can_skip_emitter(emitter)) {
return;
}
const EmitterData *light_emitter_data = light_emitter_data_map_.ensure_data_if_possible(scene,
emitter);
if (light_emitter_data) {
foreach_light_collection_object(
*emitter.light_linking->receiver_collection,
[&](const CollectionLightLinking &collection_light_linking, const Object &receiver) {
add_receiver_object(*light_emitter_data, collection_light_linking, receiver);
});
}
}
void Cache::add_shadow_linking_emitter(const Scene &scene, const Object &emitter)
{
BLI_assert(is_valid_input_id(emitter.id));
if (shadow_emitter_data_map_.can_skip_emitter(emitter)) {
return;
}
const EmitterData *shadow_emitter_data = shadow_emitter_data_map_.ensure_data_if_possible(
scene, emitter);
if (shadow_emitter_data) {
foreach_light_collection_object(
*emitter.light_linking->blocker_collection,
[&](const CollectionLightLinking &collection_light_linking, const Object &receiver) {
add_blocker_object(*shadow_emitter_data, collection_light_linking, receiver);
});
}
}
void Cache::add_receiver_object(const EmitterData &emitter_data,
const CollectionLightLinking &collection_light_linking,
const Object &receiver)
{
BLI_assert(is_valid_input_id(receiver.id));
light_linking_.link_object(
emitter_data, eCollectionLightLinkingState(collection_light_linking.link_state), receiver);
}
void Cache::add_blocker_object(const EmitterData &emitter_data,
const CollectionLightLinking &collection_light_linking,
const Object &blocker)
{
BLI_assert(is_valid_input_id(blocker.id));
shadow_linking_.link_object(
emitter_data, eCollectionLightLinkingState(collection_light_linking.link_state), blocker);
}
void Cache::end_build(const Scene &scene)
{
if (!has_light_linking()) {
return;
}
light_linking_.end_build(scene, light_emitter_data_map_);
shadow_linking_.end_build(scene, shadow_emitter_data_map_);
}
/* Set runtime data in light linking. */
void Cache::eval_runtime_data(Object &object_eval) const
{
static const LightLinkingRuntime runtime_no_links = {
EmitterSetMembership::SET_MEMBERSHIP_ALL, EmitterSetMembership::SET_MEMBERSHIP_ALL, 0, 0};
if (!has_light_linking()) {
/* No light linking used in the scene, still reset to default on objects that have
* allocated light linking data structures since we can't free them here. */
if (object_eval.light_linking) {
object_eval.light_linking->runtime = runtime_no_links;
}
return;
}
/* Receiver and blocker configuration. */
LightLinkingRuntime runtime = {};
runtime.receiver_light_set = light_linking_.get_light_set_for(object_eval);
runtime.blocker_shadow_set = shadow_linking_.get_light_set_for(object_eval);
/* Emitter configuration. */
const EmitterData *light_emitter_data = light_emitter_data_map_.get_data(object_eval);
if (light_emitter_data) {
runtime.light_set_membership = light_emitter_data->light_membership.get_mask();
}
else {
runtime.light_set_membership = EmitterSetMembership::SET_MEMBERSHIP_ALL;
}
const EmitterData *shadow_emitter_data = shadow_emitter_data_map_.get_data(object_eval);
if (shadow_emitter_data) {
runtime.shadow_set_membership = shadow_emitter_data->shadow_membership.get_mask();
}
else {
runtime.shadow_set_membership = EmitterSetMembership::SET_MEMBERSHIP_ALL;
}
const bool need_runtime = (memcmp(&runtime, &runtime_no_links, sizeof(runtime)) != 0);
/* Assign, allocating light linking on demand if needed. */
if (object_eval.light_linking) {
object_eval.light_linking->runtime = runtime;
if (!need_runtime) {
/* Note that this will only remove lazily allocated light_linking on the evaluated object,
* as an empty light_linking is not allowed on the original object. */
BKE_light_linking_free_if_empty(&object_eval);
}
}
else if (need_runtime) {
object_eval.light_linking = MEM_cnew<LightLinking>(__func__);
object_eval.light_linking->runtime = runtime;
}
}
} // namespace blender::deg::light_linking
/** \} */