Files
test/source/blender/blenkernel/intern/light_linking.cc
Hans Goudey 81a63153d0 Despgraph: Rename "copy-on-write" to "copy-on-evaluation"
The depsgraph CoW mechanism is a bit of a misnomer. It creates an
evaluated copy for data-blocks regardless of whether the copy will
actually be written to. The point is to have physical separation between
original and evaluated data. This is in contrast to the commonly used
performance improvement of keeping a user count and copying data
implicitly when it needs to be changed. In Blender code we call this
"implicit sharing" instead. Importantly, the dependency graph has no
idea about the _actual_ CoW behavior in Blender.

Renaming this functionality in the despgraph removes some of the
confusion that comes up when talking about this, and will hopefully
make the depsgraph less confusing to understand initially too. Wording
like "the evaluated copy" (as opposed to the original data-block) has
also become common anyway.

Pull Request: https://projects.blender.org/blender/blender/pulls/118338
2024-02-19 15:54:08 +01:00

497 lines
16 KiB
C++

/* SPDX-FileCopyrightText: 2001-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_light_linking.h"
#include <string>
#include "MEM_guardedalloc.h"
#include "DNA_ID.h"
#include "DNA_collection_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BLI_assert.h"
#include "BLI_string.h"
#include "BKE_collection.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_report.hh"
#include "BLT_translation.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
void BKE_light_linking_free_if_empty(Object *object)
{
if (object->light_linking->receiver_collection == nullptr &&
object->light_linking->blocker_collection == nullptr)
{
MEM_SAFE_FREE(object->light_linking);
}
}
Collection *BKE_light_linking_collection_get(const Object *object,
const LightLinkingType link_type)
{
if (!object->light_linking) {
return nullptr;
}
switch (link_type) {
case LIGHT_LINKING_RECEIVER:
return object->light_linking->receiver_collection;
case LIGHT_LINKING_BLOCKER:
return object->light_linking->blocker_collection;
}
return nullptr;
}
static std::string get_default_collection_name(const Object *object,
const LightLinkingType link_type)
{
const char *format;
switch (link_type) {
case LIGHT_LINKING_RECEIVER:
format = DATA_("Light Linking for %s");
break;
case LIGHT_LINKING_BLOCKER:
format = DATA_("Shadow Linking for %s");
break;
}
char name[MAX_ID_NAME];
SNPRINTF(name, format, object->id.name + 2);
return name;
}
Collection *BKE_light_linking_collection_new(Main *bmain,
Object *object,
const LightLinkingType link_type)
{
const std::string collection_name = get_default_collection_name(object, link_type);
Collection *new_collection = BKE_collection_add(bmain, nullptr, collection_name.c_str());
BKE_light_linking_collection_assign(bmain, object, new_collection, link_type);
return new_collection;
}
void BKE_light_linking_collection_assign_only(Object *object,
Collection *new_collection,
const LightLinkingType link_type)
{
/* Remove user from old collection. */
Collection *old_collection = BKE_light_linking_collection_get(object, link_type);
if (old_collection) {
id_us_min(&old_collection->id);
}
/* Allocate light linking on demand. */
if (new_collection && !object->light_linking) {
object->light_linking = MEM_cnew<LightLinking>(__func__);
}
if (object->light_linking) {
/* Assign and increment user of new collection. */
switch (link_type) {
case LIGHT_LINKING_RECEIVER:
object->light_linking->receiver_collection = new_collection;
break;
case LIGHT_LINKING_BLOCKER:
object->light_linking->blocker_collection = new_collection;
break;
default:
BLI_assert_unreachable();
break;
}
if (new_collection) {
id_us_plus(&new_collection->id);
}
BKE_light_linking_free_if_empty(object);
}
}
void BKE_light_linking_collection_assign(Main *bmain,
Object *object,
Collection *new_collection,
const LightLinkingType link_type)
{
BKE_light_linking_collection_assign_only(object, new_collection, link_type);
DEG_id_tag_update(&object->id, ID_RECALC_SYNC_TO_EVAL | ID_RECALC_SHADING);
DEG_relations_tag_update(bmain);
}
static CollectionObject *find_collection_object(const Collection *collection, const Object *object)
{
LISTBASE_FOREACH (CollectionObject *, collection_object, &collection->gobject) {
if (collection_object->ob == object) {
return collection_object;
}
}
return nullptr;
}
static CollectionChild *find_collection_child(const Collection *collection,
const Collection *child)
{
LISTBASE_FOREACH (CollectionChild *, collection_child, &collection->children) {
if (collection_child->collection == child) {
return collection_child;
}
}
return nullptr;
}
/* Add object to the light linking collection and return corresponding CollectionLightLinking
* settings.
*
* If the object is already in the collection then the content of the collection is not modified,
* and the existing light linking settings are returned. */
static CollectionLightLinking *light_linking_collection_add_object(Main *bmain,
Collection *collection,
Object *object)
{
BKE_collection_object_add(bmain, collection, object);
CollectionObject *collection_object = find_collection_object(collection, object);
if (!collection_object) {
BLI_assert_msg(0, "Object was not found after added to the light linking collection");
return nullptr;
}
return &collection_object->light_linking;
}
/* Add child collection to the light linking collection and return corresponding
* CollectionLightLinking settings.
*
* If the child collection is already in the collection then the content of the collection is
* not modified, and the existing light linking settings are returned. */
static CollectionLightLinking *light_linking_collection_add_collection(Main *bmain,
Collection *collection,
Collection *child)
{
BKE_collection_child_add(bmain, collection, child);
CollectionChild *collection_child = find_collection_child(collection, child);
if (!collection_child) {
BLI_assert_msg(0, "Collection was not found after added to the light linking collection");
return nullptr;
}
return &collection_child->light_linking;
}
void BKE_light_linking_add_receiver_to_collection(Main *bmain,
Collection *collection,
ID *receiver,
const eCollectionLightLinkingState link_state)
{
const ID_Type id_type = GS(receiver->name);
CollectionLightLinking *collection_light_linking = nullptr;
if (id_type == ID_OB) {
Object *object = reinterpret_cast<Object *>(receiver);
if (!OB_TYPE_IS_GEOMETRY(object->type)) {
return;
}
collection_light_linking = light_linking_collection_add_object(bmain, collection, object);
}
else if (id_type == ID_GR) {
collection_light_linking = light_linking_collection_add_collection(
bmain, collection, reinterpret_cast<Collection *>(receiver));
}
else {
return;
}
if (!collection_light_linking) {
return;
}
collection_light_linking->link_state = link_state;
DEG_id_tag_update(&collection->id, ID_RECALC_HIERARCHY);
DEG_id_tag_update(receiver, ID_RECALC_SHADING);
DEG_relations_tag_update(bmain);
}
static void order_collection_receiver_before(Collection *collection,
Collection *receiver,
const ID *before)
{
CollectionChild *receiver_collection_child = find_collection_child(collection, receiver);
if (!receiver_collection_child) {
BLI_assert_msg(0, "Receiver child was not found after adding collection to light linking");
return;
}
const ID_Type before_id_type = GS(before->name);
if (before_id_type != ID_GR) {
/* Adding before object: move the collection to the very bottom.
* This is as far to the bottom as the receiver can be in the flattened list of the collection.
*/
BLI_remlink(&collection->children, receiver_collection_child);
BLI_addtail(&collection->children, receiver_collection_child);
return;
}
CollectionChild *before_collection_child = find_collection_child(
collection, reinterpret_cast<const Collection *>(before));
if (!before_collection_child) {
BLI_assert_msg(0, "Before child was not found");
return;
}
BLI_remlink(&collection->children, receiver_collection_child);
BLI_insertlinkbefore(&collection->children, before_collection_child, receiver_collection_child);
}
static void order_collection_receiver_after(Collection *collection,
Collection *receiver,
const ID *after)
{
CollectionChild *receiver_collection_child = find_collection_child(collection, receiver);
if (!receiver_collection_child) {
BLI_assert_msg(0, "Receiver child was not found after adding collection to light linking");
return;
}
const ID_Type after_id_type = GS(after->name);
if (after_id_type != ID_GR) {
/* Adding before object: move the collection to the very bottom.
* This is as far to the bottom as the receiver can be in the flattened list of the collection.
*/
BLI_remlink(&collection->children, receiver_collection_child);
BLI_addtail(&collection->children, receiver_collection_child);
return;
}
CollectionChild *after_collection_child = find_collection_child(
collection, reinterpret_cast<const Collection *>(after));
if (!after_collection_child) {
BLI_assert_msg(0, "After child was not found");
return;
}
BLI_remlink(&collection->children, receiver_collection_child);
BLI_insertlinkafter(&collection->children, after_collection_child, receiver_collection_child);
}
static void order_object_receiver_before(Collection *collection,
Object *receiver,
const ID *before)
{
CollectionObject *receiver_collection_object = find_collection_object(collection, receiver);
if (!receiver_collection_object) {
BLI_assert_msg(
0, "Receiver collection object was not found after adding collection to light linking");
return;
}
const ID_Type before_id_type = GS(before->name);
if (before_id_type != ID_OB) {
/* Adding before collection: move the receiver to the very beginning of the child objects list.
* This is as close to the top of the flattened list of the collection content the object can
* possibly be. */
BLI_remlink(&collection->gobject, receiver_collection_object);
BLI_addhead(&collection->gobject, receiver_collection_object);
return;
}
CollectionObject *before_collection_object = find_collection_object(
collection, reinterpret_cast<const Object *>(before));
if (!before_collection_object) {
BLI_assert_msg(0, "Before collection object was not found");
return;
}
BLI_remlink(&collection->gobject, receiver_collection_object);
BLI_insertlinkbefore(&collection->gobject, before_collection_object, receiver_collection_object);
}
static void order_object_receiver_after(Collection *collection, Object *receiver, const ID *after)
{
CollectionObject *receiver_collection_object = find_collection_object(collection, receiver);
if (!receiver_collection_object) {
BLI_assert_msg(
0, "Receiver collection object was not found after adding collection to light linking");
return;
}
const ID_Type after_id_type = GS(after->name);
if (after_id_type != ID_OB) {
/* Adding after collection: move the receiver to the very beginning of the child objects list.
* This is as close to the top of the flattened list of the collection content the object can
* possibly be. */
BLI_remlink(&collection->gobject, receiver_collection_object);
BLI_addhead(&collection->gobject, receiver_collection_object);
return;
}
CollectionObject *after_collection_object = find_collection_object(
collection, reinterpret_cast<const Object *>(after));
if (!after_collection_object) {
BLI_assert_msg(0, "After collection object was not found");
return;
}
BLI_remlink(&collection->gobject, receiver_collection_object);
BLI_insertlinkafter(&collection->gobject, after_collection_object, receiver_collection_object);
}
void BKE_light_linking_add_receiver_to_collection_before(
Main *bmain,
Collection *collection,
ID *receiver,
const ID *before,
const eCollectionLightLinkingState link_state)
{
BLI_assert(before);
BKE_light_linking_add_receiver_to_collection(bmain, collection, receiver, link_state);
if (!before) {
return;
}
const ID_Type id_type = GS(receiver->name);
if (id_type == ID_OB) {
order_object_receiver_before(collection, reinterpret_cast<Object *>(receiver), before);
}
else if (id_type == ID_GR) {
order_collection_receiver_before(collection, reinterpret_cast<Collection *>(receiver), before);
}
}
void BKE_light_linking_add_receiver_to_collection_after(
Main *bmain,
Collection *collection,
ID *receiver,
const ID *after,
const eCollectionLightLinkingState link_state)
{
BLI_assert(after);
BKE_light_linking_add_receiver_to_collection(bmain, collection, receiver, link_state);
if (!after) {
return;
}
const ID_Type id_type = GS(receiver->name);
if (id_type == ID_OB) {
order_object_receiver_after(collection, reinterpret_cast<Object *>(receiver), after);
}
else if (id_type == ID_GR) {
order_collection_receiver_after(collection, reinterpret_cast<Collection *>(receiver), after);
}
}
bool BKE_light_linking_unlink_id_from_collection(Main *bmain,
Collection *collection,
ID *id,
ReportList *reports)
{
const ID_Type id_type = GS(id->name);
if (id_type == ID_OB) {
BKE_collection_object_remove(bmain, collection, reinterpret_cast<Object *>(id), false);
}
else if (id_type == ID_GR) {
BKE_collection_child_remove(bmain, collection, reinterpret_cast<Collection *>(id));
}
else {
BKE_reportf(reports,
RPT_ERROR,
"Cannot unlink unsupported '%s' from light linking collection '%s'",
id->name + 2,
collection->id.name + 2);
return false;
}
DEG_id_tag_update(&collection->id, ID_RECALC_HIERARCHY);
DEG_relations_tag_update(bmain);
return true;
}
void BKE_light_linking_link_receiver_to_emitter(Main *bmain,
Object *emitter,
Object *receiver,
const LightLinkingType link_type,
const eCollectionLightLinkingState link_state)
{
if (!OB_TYPE_IS_GEOMETRY(receiver->type)) {
return;
}
Collection *collection = BKE_light_linking_collection_get(emitter, link_type);
if (!collection) {
collection = BKE_light_linking_collection_new(bmain, emitter, link_type);
}
BKE_light_linking_add_receiver_to_collection(bmain, collection, &receiver->id, link_state);
}
void BKE_light_linking_select_receivers_of_emitter(Scene *scene,
ViewLayer *view_layer,
Object *emitter,
const LightLinkingType link_type)
{
Collection *collection = BKE_light_linking_collection_get(emitter, link_type);
if (!collection) {
return;
}
BKE_view_layer_synced_ensure(scene, view_layer);
/* Deselect all currently selected objects in the view layer, but keep the emitter selected.
* This is because the operation is called from the emitter being active, and it will be
* confusing to deselect it but keep active. */
LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) {
if (base->object == emitter) {
continue;
}
base->flag &= ~BASE_SELECTED;
}
/* Select objects which are reachable via the receiver collection hierarchy. */
LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
Base *base = BKE_view_layer_base_find(view_layer, cob->ob);
if (!base) {
continue;
}
/* TODO(sergey): Check whether the object is configured to receive light. */
base->flag |= BASE_SELECTED;
}
DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
}