Light linking: Initial support of shadow linking #106501

Merged
Sergey Sharybin merged 2 commits from Sergey/blender:cycles-light-linking-shadow into cycles-light-linking 2023-04-04 10:18:52 +02:00
31 changed files with 633 additions and 303 deletions

View File

@ -148,7 +148,10 @@ void BlenderSync::sync_light(BL::Object &b_parent,
/* Light group and linking. */
light->set_lightgroup(ustring(b_ob_info.real_object.lightgroup()));
light->set_light_link_set_membership(light_linking_get_set_membership(b_ob_info.real_object));
light->set_light_set_membership(
BlenderLightLink::get_light_set_membership(b_ob_info.real_object));
light->set_shadow_set_membership(
BlenderLightLink::get_shadow_set_membership(b_ob_info.real_object));
/* tag */
light->tag_update(scene);

View File

@ -12,16 +12,28 @@ static const ::Object *get_blender_object(const BL::Object &object)
return reinterpret_cast<::Object *>(object.ptr.data);
}
uint64_t light_linking_get_set_membership(const BL::Object &object)
uint64_t BlenderLightLink::get_light_set_membership(const BL::Object &object)
{
const ::Object *blender_object = get_blender_object(object);
return blender_object->light_linking.runtime.set_membership;
return blender_object->light_linking.runtime.light_set_membership;
}
uint light_linking_get_receiver_set(const BL::Object &object)
uint BlenderLightLink::get_receiver_light_set(const BL::Object &object)
{
const ::Object *blender_object = get_blender_object(object);
return blender_object->light_linking.runtime.receiver_set;
return blender_object->light_linking.runtime.receiver_light_set;
}
uint64_t BlenderLightLink::get_shadow_set_membership(const BL::Object &object)
{
const ::Object *blender_object = get_blender_object(object);
return blender_object->light_linking.runtime.shadow_set_membership;
}
uint BlenderLightLink::get_blocker_shadow_set(const BL::Object &object)
{
const ::Object *blender_object = get_blender_object(object);
return blender_object->light_linking.runtime.blocker_shadow_set;
}
CCL_NAMESPACE_END

View File

@ -10,7 +10,13 @@
CCL_NAMESPACE_BEGIN
uint64_t light_linking_get_set_membership(const BL::Object &object);
uint light_linking_get_receiver_set(const BL::Object &object);
class BlenderLightLink {
public:
static uint64_t get_light_set_membership(const BL::Object &object);
static uint get_receiver_light_set(const BL::Object &object);
static uint64_t get_shadow_set_membership(const BL::Object &object);
static uint get_blocker_shadow_set(const BL::Object &object);
};
CCL_NAMESPACE_END

View File

@ -349,8 +349,10 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph,
/* Light group and linking. */
object->set_lightgroup(ustring(b_ob.lightgroup()));
object->set_light_link_set_membership(light_linking_get_set_membership(b_ob));
object->set_light_link_receiver_set(light_linking_get_receiver_set(b_ob));
object->set_light_set_membership(BlenderLightLink::get_light_set_membership(b_ob));
object->set_receiver_light_set(BlenderLightLink::get_receiver_light_set(b_ob));
object->set_shadow_set_membership(BlenderLightLink::get_shadow_set_membership(b_ob));
object->set_blocker_shadow_set(BlenderLightLink::get_blocker_shadow_set(b_ob));
object->tag_update(scene);
}

View File

@ -144,6 +144,12 @@ ccl_device_inline
continue;
}
#ifdef __LIGHT_LINKING__
if (intersection_skip_shadow_link(kg, ray, prim_object)) {
continue;
}
#endif
switch (type & PRIMITIVE_ALL) {
case PRIMITIVE_TRIANGLE: {
hit = triangle_intersect(

View File

@ -129,6 +129,12 @@ ccl_device_noinline bool BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg,
continue;
}
#ifdef __LIGHT_LINKING__
if (intersection_skip_shadow_link(kg, ray, prim_object)) {
continue;
}
#endif
switch (type & PRIMITIVE_ALL) {
case PRIMITIVE_TRIANGLE: {
if (triangle_intersect(kg,

View File

@ -232,4 +232,41 @@ ccl_device_inline bool intersection_skip_self_local(ccl_private const RaySelfPri
return (self.prim == prim);
}
#ifdef __LIGHT_LINKING__
ccl_device_inline uint64_t ray_get_shadow_set_membership(KernelGlobals kg,
ccl_private const Ray *ray)
{
if (ray->self.light != LAMP_NONE) {
return kernel_data_fetch(lights, ray->self.light).shadow_set_membership;
}
if (ray->self.light_object != OBJECT_NONE) {
return kernel_data_fetch(objects, ray->self.light_object).shadow_set_membership;
}
return LIGHT_LINK_MASK_ALL;
}
#endif
ccl_device_inline bool intersection_skip_shadow_link(KernelGlobals kg,
ccl_private const Ray *ray,
const int isect_object)
{
#ifdef __LIGHT_LINKING__
if (!(kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_LINKING)) {
return false;
}
const uint64_t set_membership = ray_get_shadow_set_membership(kg, ray);
if (set_membership == LIGHT_LINK_MASK_ALL) {
return false;
}
const uint blocker_set = kernel_data_fetch(objects, isect_object).blocker_shadow_set;
return ((uint64_t(1) << uint64_t(blocker_set)) & set_membership) == 0;
#else
return false;
#endif
}
CCL_NAMESPACE_END

View File

@ -118,14 +118,19 @@ ccl_device_inline void kernel_embree_setup_rayhit(const Ray &ray,
rayhit.hit.instID[0] = RTC_INVALID_GEOMETRY_ID;
}
ccl_device_inline int kernel_embree_get_hit_object(const RTCHit *hit)
{
return (hit->instID[0] != RTC_INVALID_GEOMETRY_ID ? hit->instID[0] : hit->geomID) / 2;
}
ccl_device_inline bool kernel_embree_is_self_intersection(const KernelGlobals kg,
const RTCHit *hit,
const Ray *ray,
const intptr_t prim_offset)
{
int object, prim;
object = (hit->instID[0] != RTC_INVALID_GEOMETRY_ID ? hit->instID[0] : hit->geomID) / 2;
const int object = kernel_embree_get_hit_object(hit);
int prim;
if ((ray->self.object == object) || (ray->self.light_object == object)) {
prim = hit->primID + prim_offset;
}
@ -149,7 +154,7 @@ ccl_device_inline void kernel_embree_convert_hit(KernelGlobals kg,
{
isect->t = ray->tfar;
isect->prim = hit->primID + prim_offset;
isect->object = hit->instID[0] != RTC_INVALID_GEOMETRY_ID ? hit->instID[0] / 2 : hit->geomID / 2;
isect->object = kernel_embree_get_hit_object(hit);
const bool is_hair = hit->geomID & 1;
if (is_hair) {
@ -218,7 +223,15 @@ ccl_device void kernel_embree_filter_intersection_func(const RTCFilterFunctionNA
if (kernel_embree_is_self_intersection(
kg, hit, cray, reinterpret_cast<intptr_t>(args->geometryUserPtr))) {
*args->valid = 0;
return;
}
#ifdef __LIGHT_LINKING__
if (intersection_skip_shadow_link(kg, cray, kernel_embree_get_hit_object(hit))) {
*args->valid = 0;
return;
}
#endif
}
/* This gets called by Embree at every valid ray/object intersection.
@ -246,6 +259,14 @@ ccl_device void kernel_embree_filter_occluded_func(const RTCFilterFunctionNArgum
*args->valid = 0;
return;
}
#ifdef __LIGHT_LINKING__
if (intersection_skip_shadow_link(kg, cray, current_isect.object)) {
*args->valid = 0;
return;
}
#endif
/* If no transparent shadows or max number of hits exceeded, all light is blocked. */
const int flags = intersection_get_shader_flags(kg, current_isect.prim, current_isect.type);
if (!(flags & (SD_HAS_TRANSPARENT_SHADOW)) || ctx->num_hits >= ctx->max_hits) {

View File

@ -183,6 +183,12 @@ extern "C" __global__ void __anyhit__kernel_optix_shadow_all_hit()
return optixIgnoreIntersection();
}
# ifdef __LIGHT_LINKING__
if (intersection_skip_shadow_link(nullptr, ray, object)) {
return optixIgnoreIntersection();
}
# endif
# ifndef __TRANSPARENT_SHADOWS__
/* No transparent shadows support compiled in, make opaque. */
optixSetPayload_5(true);
@ -325,6 +331,12 @@ extern "C" __global__ void __anyhit__kernel_optix_visibility_test()
ccl_private Ray *const ray = get_payload_ptr_6<Ray>();
if (visibility & PATH_RAY_SHADOW_OPAQUE) {
#ifdef __LIGHT_LINKING__
if (intersection_skip_shadow_link(nullptr, ray, object)) {
return optixIgnoreIntersection();
}
#endif
if (intersection_skip_self_shadow(ray->self, object, prim)) {
return optixIgnoreIntersection();
}

View File

@ -353,6 +353,7 @@ ccl_device void integrator_intersect_closest(KernelGlobals kg,
ray.self.prim = last_isect_prim;
ray.self.light_object = OBJECT_NONE;
ray.self.light_prim = PRIM_NONE;
ray.self.light = LAMP_NONE;
bool hit = scene_intersect(kg, &ray, visibility, &isect);
/* TODO: remove this and do it in the various intersection functions instead. */

View File

@ -147,6 +147,7 @@ ccl_device void integrator_intersect_shadow(KernelGlobals kg, IntegratorShadowSt
ray.self.prim = INTEGRATOR_STATE_ARRAY(state, shadow_isect, 0, prim);
ray.self.light_object = INTEGRATOR_STATE_ARRAY(state, shadow_isect, 1, object);
ray.self.light_prim = INTEGRATOR_STATE_ARRAY(state, shadow_isect, 1, prim);
ray.self.light = INTEGRATOR_STATE_ARRAY(state, shadow_isect, 2, object);
/* Compute visibility. */
const uint visibility = integrate_intersect_shadow_visibility(kg, state);

View File

@ -29,6 +29,7 @@ ccl_device void integrator_volume_stack_update_for_subsurface(KernelGlobals kg,
volume_ray.self.prim = INTEGRATOR_STATE(state, isect, prim);
volume_ray.self.light_object = OBJECT_NONE;
volume_ray.self.light_prim = PRIM_NONE;
volume_ray.self.light = LAMP_NONE;
/* Store to avoid global fetches on every intersection step. */
const uint volume_stack_size = kernel_data.volume_stack_size;
@ -84,6 +85,7 @@ ccl_device void integrator_volume_stack_init(KernelGlobals kg, IntegratorState s
volume_ray.self.prim = PRIM_NONE;
volume_ray.self.light_object = OBJECT_NONE;
volume_ray.self.light_prim = PRIM_NONE;
volume_ray.self.light = LAMP_NONE;
int stack_index = 0, enclosed_index = 0;

View File

@ -407,6 +407,7 @@ ccl_device_forceinline bool mnee_newton_solver(KernelGlobals kg,
Ray projection_ray;
projection_ray.self.light_object = OBJECT_NONE;
projection_ray.self.light_prim = PRIM_NONE;
projection_ray.self.light = LAMP_NONE;
projection_ray.dP = differential_make_compact(sd->dP);
projection_ray.dD = differential_zero_compact();
projection_ray.tmin = 0.0f;
@ -823,6 +824,7 @@ ccl_device_forceinline bool mnee_path_contribution(KernelGlobals kg,
Ray probe_ray;
probe_ray.self.light_object = ls->object;
probe_ray.self.light_prim = ls->prim;
probe_ray.self.light = ls->lamp;
probe_ray.tmin = 0.0f;
probe_ray.dP = differential_make_compact(sd->dP);
probe_ray.dD = differential_zero_compact();
@ -923,6 +925,7 @@ ccl_device_forceinline int kernel_path_mnee_sample(KernelGlobals kg,
probe_ray.self.prim = sd->prim;
probe_ray.self.light_object = ls->object;
probe_ray.self.light_prim = ls->prim;
probe_ray.self.light = ls->lamp;
probe_ray.P = sd->P;
probe_ray.tmin = 0.0f;
if (ls->t == FLT_MAX) {

View File

@ -75,6 +75,7 @@ ccl_device_inline void integrate_transparent_volume_shadow(KernelGlobals kg,
ray.self.prim = PRIM_NONE;
ray.self.light_object = OBJECT_NONE;
ray.self.light_prim = PRIM_NONE;
ray.self.light = LAMP_NONE;
/* Modify ray position and length to match current segment. */
ray.tmin = (hit == 0) ? ray.tmin : INTEGRATOR_STATE_ARRAY(state, shadow_isect, hit - 1, t);
ray.tmax = (hit < num_recorded_hits) ? INTEGRATOR_STATE_ARRAY(state, shadow_isect, hit, t) :

View File

@ -265,6 +265,7 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg,
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 0, prim) = ray.self.prim;
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 1, object) = ray.self.light_object;
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 1, prim) = ray.self.light_prim;
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 2, object) = ray.self.light;
/* Copy state from main path to shadow path. */
uint32_t shadow_flag = INTEGRATOR_STATE(state, path, flag);
@ -546,6 +547,7 @@ ccl_device_forceinline void integrate_surface_ao(KernelGlobals kg,
ray.self.prim = (skip_self) ? sd->prim : PRIM_NONE;
ray.self.light_object = OBJECT_NONE;
ray.self.light_prim = PRIM_NONE;
ray.self.light = LAMP_NONE;
ray.dP = differential_zero_compact();
ray.dD = differential_zero_compact();
@ -562,6 +564,7 @@ ccl_device_forceinline void integrate_surface_ao(KernelGlobals kg,
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 0, prim) = ray.self.prim;
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 1, object) = ray.self.light_object;
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 1, prim) = ray.self.light_prim;
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 2, object) = ray.self.light;
/* Copy state from main path to shadow path. */
const uint16_t bounce = INTEGRATOR_STATE(state, path, bounce);

View File

@ -91,7 +91,8 @@ ccl_device_inline bool subsurface_disk(KernelGlobals kg,
ray.self.object = OBJECT_NONE;
ray.self.prim = PRIM_NONE;
ray.self.light_object = OBJECT_NONE;
ray.self.light_prim = OBJECT_NONE;
ray.self.light_prim = PRIM_NONE;
ray.self.light = LAMP_NONE;
/* Intersect with the same object. if multiple intersections are found it
* will use at most BSSRDF_MAX_HITS hits, a random subset of all hits. */

View File

@ -197,6 +197,7 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg,
ray.self.prim = prim;
ray.self.light_object = OBJECT_NONE;
ray.self.light_prim = PRIM_NONE;
ray.self.light = LAMP_NONE;
/* Convert subsurface to volume coefficients.
* The single-scattering albedo is named alpha to avoid confusion with the surface albedo. */

View File

@ -52,12 +52,11 @@ ccl_device_inline bool light_link_light_match(KernelGlobals kg,
return true;
}
const uint64_t set_membership =
kernel_data_fetch(lights, light_emitter).light_link_set_membership;
const uint64_t set_membership = kernel_data_fetch(lights, light_emitter).light_set_membership;
if (set_membership == LIGHT_LINK_MASK_ALL) {
return true;
}
const uint receiver_set = kernel_data_fetch(objects, object_receiver).light_link_receiver_set;
const uint receiver_set = kernel_data_fetch(objects, object_receiver).receiver_light_set;
return ((uint64_t(1) << uint64_t(receiver_set)) & set_membership) != 0;
}
@ -72,12 +71,11 @@ ccl_device_inline bool light_link_object_match(KernelGlobals kg,
return true;
}
const uint64_t set_membership =
kernel_data_fetch(objects, object_emitter).light_link_set_membership;
const uint64_t set_membership = kernel_data_fetch(objects, object_emitter).light_set_membership;
if (set_membership == LIGHT_LINK_MASK_ALL) {
return true;
}
const uint receiver_set = kernel_data_fetch(objects, object_receiver).light_link_receiver_set;
const uint receiver_set = kernel_data_fetch(objects, object_receiver).receiver_light_set;
return ((uint64_t(1) << uint64_t(receiver_set)) & set_membership) != 0;
}

View File

@ -250,6 +250,7 @@ ccl_device_inline void shadow_ray_setup(ccl_private const ShaderData *ccl_restri
ray->self.prim = (skip_self) ? sd->prim : PRIM_NONE;
ray->self.light_object = ls->object;
ray->self.light_prim = ls->prim;
ray->self.light = ls->lamp;
}
/* Create shadow ray towards light sample. */

View File

@ -1676,6 +1676,7 @@ bool OSLRenderServices::trace(TraceOpt &options,
ray.self.prim = PRIM_NONE;
ray.self.light_object = OBJECT_NONE;
ray.self.light_prim = PRIM_NONE;
ray.self.light = LAMP_NONE;
if (options.mindist == 0.0f) {
/* avoid self-intersections */

View File

@ -66,6 +66,7 @@ ccl_device float svm_ao(
ray.self.prim = sd->prim;
ray.self.light_object = OBJECT_NONE;
ray.self.light_prim = PRIM_NONE;
ray.self.light = LAMP_NONE;
ray.dP = differential_zero_compact();
ray.dD = differential_zero_compact();

View File

@ -188,6 +188,7 @@ ccl_device float3 svm_bevel(
ray.self.prim = PRIM_NONE;
ray.self.light_object = OBJECT_NONE;
ray.self.light_prim = PRIM_NONE;
ray.self.light = LAMP_NONE;
/* Intersect with the same object. if multiple intersections are found it
* will use at most LOCAL_MAX_HITS hits, a random subset of all hits. */

View File

@ -61,6 +61,7 @@ CCL_NAMESPACE_BEGIN
#define __DENOISING_FEATURES__
#define __DPDU__
#define __HAIR__
#define __LIGHT_LINKING__
#define __LIGHT_TREE__
#define __OBJECT_MOTION__
#define __PASSES__
@ -562,6 +563,7 @@ typedef struct RaySelfPrimitives {
int object; /* Instance prim is a part of */
int light_prim; /* Light primitive */
int light_object; /* Light object */
int light; /* Light ID (the light the shadow ray is traced towards to) */
} RaySelfPrimitives;
typedef struct Ray {
@ -1278,9 +1280,10 @@ typedef struct KernelObject {
float velocity_scale;
/* TODO: separate array to avoid memory overhead when not used. */
uint64_t light_link_set_membership;
uint light_link_receiver_set;
uint pad;
uint64_t light_set_membership;
uint receiver_light_set;
uint64_t shadow_set_membership;
uint blocker_shadow_set;
} KernelObject;
static_assert_align(KernelObject, 16);
@ -1346,8 +1349,8 @@ typedef struct KernelLight {
KernelAreaLight area;
KernelDistantLight distant;
};
uint64_t light_link_set_membership;
uint64_t pad;
uint64_t light_set_membership;
uint64_t shadow_set_membership;
} KernelLight;
static_assert_align(KernelLight, 16);

View File

@ -137,7 +137,8 @@ NODE_DEFINE(Light)
SOCKET_NODE(shader, "Shader", Shader::get_node_type());
SOCKET_STRING(lightgroup, "Light Group", ustring());
SOCKET_UINT64(light_link_set_membership, "Light Link Set Membership", 0);
SOCKET_UINT64(light_set_membership, "Light Set Membership", 0);
SOCKET_UINT64(shadow_set_membership, "Shadow Set Membership", 0);
return type;
}
@ -1128,6 +1129,12 @@ void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Sce
klights[light_index].spot.spot_smooth = spot_smooth;
}
/* Disable MIS if the light participates in the shadow linking, as it is not supported. */
/* TODO(sergey): Support MIS with shadow linking. */
if (light->shadow_set_membership) {
shader_id &= ~SHADER_USE_MIS;
}
klights[light_index].shader_id = shader_id;
klights[light_index].max_bounces = max_bounces;
@ -1145,7 +1152,8 @@ void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Sce
klights[light_index].lightgroup = LIGHTGROUP_NONE;
}
klights[light_index].light_link_set_membership = light->light_link_set_membership;
klights[light_index].light_set_membership = light->light_set_membership;
klights[light_index].shadow_set_membership = light->shadow_set_membership;
light_index++;
}

View File

@ -73,7 +73,8 @@ class Light : public Node {
NODE_SOCKET_API(uint, random_id)
NODE_SOCKET_API(ustring, lightgroup)
NODE_SOCKET_API(uint64_t, light_link_set_membership);
NODE_SOCKET_API(uint64_t, light_set_membership);
NODE_SOCKET_API(uint64_t, shadow_set_membership);
void tag_update(Scene *scene);

View File

@ -100,8 +100,10 @@ NODE_DEFINE(Object)
SOCKET_FLOAT(ao_distance, "AO Distance", 0.0f);
SOCKET_STRING(lightgroup, "Light Group", ustring());
SOCKET_UINT(light_link_receiver_set, "Light Link Set Index", 0);
SOCKET_UINT64(light_link_set_membership, "Light Link Set Membership", 0);
SOCKET_UINT(receiver_light_set, "Light Set Index", 0);
SOCKET_UINT64(light_set_membership, "Light Set Membership", 0);
SOCKET_UINT(blocker_shadow_set, "Shadow Set Index", 0);
SOCKET_UINT64(shadow_set_membership, "Shadow Set Membership", 0);
return type;
}
@ -429,10 +431,14 @@ void ObjectManager::device_update_object_transform(UpdateObjectTransformState *s
kobject.particle_index = particle_index;
kobject.motion_offset = 0;
kobject.ao_distance = ob->ao_distance;
kobject.light_link_receiver_set = ob->light_link_receiver_set >= LIGHT_LINK_SET_MAX ?
0 :
ob->light_link_receiver_set;
kobject.light_link_set_membership = ob->light_link_set_membership;
kobject.receiver_light_set = ob->receiver_light_set >= LIGHT_LINK_SET_MAX ?
0 :
ob->receiver_light_set;
kobject.light_set_membership = ob->light_set_membership;
kobject.blocker_shadow_set = ob->blocker_shadow_set >= LIGHT_LINK_SET_MAX ?
0 :
ob->blocker_shadow_set;
kobject.shadow_set_membership = ob->shadow_set_membership;
if (geom->get_use_motion_blur()) {
state->have_motion = true;

View File

@ -67,8 +67,10 @@ class Object : public Node {
NODE_SOCKET_API(float, ao_distance)
NODE_SOCKET_API(ustring, lightgroup)
NODE_SOCKET_API(uint, light_link_receiver_set)
NODE_SOCKET_API(uint64_t, light_link_set_membership)
NODE_SOCKET_API(uint, receiver_light_set)
NODE_SOCKET_API(uint64_t, light_set_membership)
NODE_SOCKET_API(uint, blocker_shadow_set)
NODE_SOCKET_API(uint64_t, shadow_set_membership)
/* Set during device update. */
bool intersects_volume;

View File

@ -542,7 +542,7 @@ void Scene::update_kernel_features()
else if (geom->is_pointcloud()) {
kernel_features |= KERNEL_FEATURE_POINTCLOUD;
}
if (object->get_light_link_set_membership()) {
if (object->get_light_set_membership() || object->get_shadow_set_membership()) {
kernel_features |= KERNEL_FEATURE_LIGHT_LINKING;
}
}
@ -551,7 +551,7 @@ void Scene::update_kernel_features()
if (light->get_use_caustics()) {
has_caustics_light = true;
}
if (light->get_light_link_set_membership()) {
if (light->get_light_set_membership() || light->get_shadow_set_membership()) {
kernel_features |= KERNEL_FEATURE_LIGHT_LINKING;
}
}

View File

@ -50,31 +50,6 @@ void eval_runtime_data(const ::Depsgraph *depsgraph, Object &object_eval)
/** \name Internal builder API
* \{ */
namespace blender::deg::light_linking {
namespace internal {
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_2(get_default_hash(include_collection_mask),
get_default_hash(exclude_collection_mask));
}
uint64_t EmitterData::get_set_membership() const
{
const uint64_t effective_included_mask = included_sets_mask ? included_sets_mask :
SET_MEMBERSHIP_ALL;
return effective_included_mask & ~excluded_sets_mask;
}
} // namespace internal
namespace {
/* TODO(sergey): Move to a public API, solving the const-correctness. */
@ -86,47 +61,19 @@ template<class T> static inline const T *get_original(const T *id)
return reinterpret_cast<T *>(DEG_get_original_id(const_cast<ID *>(&id->id)));
}
/* 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);
}
} // namespace
LISTBASE_FOREACH (const CollectionObject *, collection_object, &collection.gobject) {
callback(collection_light_linking, *collection_object->ob);
}
}
namespace blender::deg::light_linking {
/* 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);
}
using LightSet = internal::LightSet;
using EmitterData = internal::EmitterData;
using EmitterDataMap = internal::EmitterDataMap;
using EmitterSetMembership = internal::EmitterSetMembership;
using LinkingData = internal::LinkingData;
LISTBASE_FOREACH (const CollectionObject *, collection_object, &collection.gobject) {
callback(collection_object->light_linking, *collection_object->ob);
}
}
namespace internal {
namespace {
/* Helper class which takes care of allocating an unique light set IDs, performing checks for
* overflows. */
@ -173,160 +120,38 @@ class LightSetIDManager {
} // namespace
void Cache::clear()
/* 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_2(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()
{
light_linked_sets_.clear();
emitter_data_map_.clear();
receiver_light_sets_.clear();
next_collection_id_ = 0;
}
void Cache::add_emitter(const Scene &scene, const Object &emitter)
{
BLI_assert(DEG_is_original_id(&emitter.id));
if (can_skip_emitter(emitter)) {
return;
}
const LightLinking &light_linking = emitter.light_linking;
Collection *collection = light_linking.collection;
/* Add collection bit to all receivers affected by this emitter or any emitter with the same
* receiver collection. */
/* TODO: optimize by doing this non-recursively, and only going recursive in end_build()? */
foreach_light_collection_object(
*collection,
[&](const CollectionLightLinking &collection_light_linking, const Object &receiver) {
add_receiver_object(scene, emitter, collection_light_linking, receiver);
});
}
void Cache::add_receiver_object(const Scene &scene,
const Object &emitter,
const CollectionLightLinking &collection_light_linking,
const Object &receiver)
{
BLI_assert(DEG_is_original_id(&emitter->id));
BLI_assert(DEG_is_original_id(&receiver->id));
if (receiver.light_linking.collection != nullptr) {
/* If the object has receiver collection configure do not consider it as a receiver, avoiding
* dependency cycles. */
return;
}
/* Light linking membership. */
if (collection_light_linking.light_state != COLLECTION_LIGHT_LINKING_STATE_NONE) {
const EmitterData *emitter_data = ensure_emitter_data_if_possible(scene, emitter);
if (emitter_data) {
LightSet &light_set = ensure_light_linked_set(receiver);
switch (collection_light_linking.light_state) {
case COLLECTION_LIGHT_LINKING_STATE_NONE:
break;
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;
}
}
}
/* TODO(sergey): Handle shadow linking. */
}
void Cache::end_build(const Scene &scene)
{
if (!has_light_linking()) {
return;
}
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;
receiver_light_sets_.add(receiver, light_set_id);
for (EmitterData &emitter_data : emitter_data_map_.values()) {
if (emitter_data.collection_mask & light_set.include_collection_mask) {
emitter_data.included_sets_mask |= light_set_mask;
}
if (emitter_data.collection_mask & light_set.exclude_collection_mask) {
emitter_data.excluded_sets_mask |= light_set_mask;
}
}
}
light_linked_sets_.clear_and_shrink();
}
/* Set runtime data in light linking. */
void Cache::eval_runtime_data(Object &object_eval) const
{
LightLinking &light_linking = object_eval.light_linking;
if (!has_light_linking()) {
/* No light linking used in the scene. */
light_linking.runtime.receiver_set = 0;
light_linking.runtime.set_membership = 0;
return;
}
/* Receiver configuration. */
const Object *object_orig = get_original(&object_eval);
light_linking.runtime.receiver_set = receiver_light_sets_.lookup_default(object_orig,
LightSet::DEFAULT_ID);
/* Emitter configuration. */
const EmitterData *emitter_data = get_emitter_data(object_eval);
if (emitter_data) {
light_linking.runtime.set_membership = emitter_data->get_set_membership();
}
else {
light_linking.runtime.set_membership = EmitterData::SET_MEMBERSHIP_ALL;
}
}
internal::LightSet &Cache::ensure_light_linked_set(const Object &receiver)
{
BLI_assert(DEG_is_original_id(&receiver.id));
return light_linked_sets_.lookup_or_add_as(&receiver);
}
bool Cache::can_skip_emitter(const Object &emitter) const
{
BLI_assert(DEG_is_original_id(&emitter.id));
const LightLinking &light_linking = emitter.light_linking;
const Collection *collection = light_linking.collection;
if (!collection) {
return true;
}
return emitter_data_map_.contains(collection);
}
internal::EmitterData *Cache::ensure_emitter_data_if_possible(const Scene &scene,
const Object &emitter)
EmitterData *EmitterDataMap::ensure_data_if_possible(const Scene &scene, const Object &emitter)
{
BLI_assert(DEG_is_original_id(&emitter.id));
BLI_assert(emitter.light_linking.collection);
@ -370,7 +195,7 @@ internal::EmitterData *Cache::ensure_emitter_data_if_possible(const Scene &scene
return &emitter_data;
}
const internal::EmitterData *Cache::get_emitter_data(const Object &emitter) const
const EmitterData *EmitterDataMap::get_data(const Object &emitter) const
{
const LightLinking &light_linking = emitter.light_linking;
const Collection *collection_eval = light_linking.collection;
@ -384,6 +209,257 @@ const internal::EmitterData *Cache::get_emitter_data(const Object &emitter) cons
return emitter_data_map_.lookup_ptr(collection_orig);
}
bool EmitterDataMap::can_skip_emitter(const Object &emitter) const
{
BLI_assert(DEG_is_original_id(&emitter.id));
const LightLinking &light_linking = emitter.light_linking;
const Collection *collection = light_linking.collection;
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)
{
if (link_state == COLLECTION_LIGHT_LINKING_STATE_NONE) {
return;
}
LightSet &light_set = ensure_light_set_for(object);
switch (link_state) {
case COLLECTION_LIGHT_LINKING_STATE_NONE:
BLI_assert_unreachable();
break;
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(DEG_is_original_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()
{
emitter_data_map_.clear();
light_linking_.clear();
shadow_linking_.clear();
}
void Cache::add_emitter(const Scene &scene, const Object &emitter)
{
BLI_assert(DEG_is_original_id(&emitter.id));
if (emitter_data_map_.can_skip_emitter(emitter)) {
return;
}
const LightLinking &light_linking = emitter.light_linking;
Collection *collection = light_linking.collection;
const EmitterData *emitter_data = emitter_data_map_.ensure_data_if_possible(scene, emitter);
if (!emitter_data) {
/* The number of possible emitters exceeded in the scene. */
return;
}
/* Add collection bit to all receivers affected by this emitter or any emitter with the same
* receiver collection. */
foreach_light_collection_object(
*collection,
[&](const CollectionLightLinking &collection_light_linking, const Object &receiver) {
add_receiver_object(*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(DEG_is_original_id(&receiver.id));
if (receiver.light_linking.collection != nullptr) {
/* If the object has receiver collection configure do not consider it as a receiver, avoiding
* dependency cycles. */
return;
}
/* Light linking. */
light_linking_.link_object(
emitter_data, eCollectionLightLinkingState(collection_light_linking.light_state), receiver);
/* Shadow linking. */
shadow_linking_.link_object(
emitter_data, eCollectionLightLinkingState(collection_light_linking.shadow_state), receiver);
}
void Cache::end_build(const Scene &scene)
{
if (!has_light_linking()) {
return;
}
light_linking_.end_build(scene, emitter_data_map_);
shadow_linking_.end_build(scene, emitter_data_map_);
}
/* Set runtime data in light linking. */
void Cache::eval_runtime_data(Object &object_eval) const
{
LightLinking &light_linking = object_eval.light_linking;
if (!has_light_linking()) {
/* No light linking used in the scene. */
light_linking.runtime.receiver_light_set = 0;
light_linking.runtime.light_set_membership = 0;
light_linking.runtime.blocker_shadow_set = 0;
light_linking.runtime.shadow_set_membership = 0;
return;
}
/* Receiver and blocker configuration. */
light_linking.runtime.receiver_light_set = light_linking_.get_light_set_for(object_eval);
light_linking.runtime.blocker_shadow_set = shadow_linking_.get_light_set_for(object_eval);
/* Emitter configuration. */
const EmitterData *emitter_data = emitter_data_map_.get_data(object_eval);
if (emitter_data) {
light_linking.runtime.light_set_membership = emitter_data->light_membership.get_mask();
light_linking.runtime.shadow_set_membership = emitter_data->shadow_membership.get_mask();
}
else {
light_linking.runtime.light_set_membership = EmitterSetMembership::SET_MEMBERSHIP_ALL;
light_linking.runtime.shadow_set_membership = EmitterSetMembership::SET_MEMBERSHIP_ALL;
}
}
} // namespace blender::deg::light_linking
/* \} */

View File

@ -11,6 +11,8 @@
#include "BLI_map.hh"
#include "DNA_collection_types.h" /* eCollectionLightLinkingState */
struct Collection;
struct CollectionLightLinking;
struct Object;
@ -46,6 +48,19 @@ class LightSet {
uint64_t exclude_collection_mask;
};
class EmitterSetMembership {
public:
/* Bitmask which indicates the emitter belongs to all light sets. */
static constexpr uint64_t SET_MEMBERSHIP_ALL = ~uint64_t(0);
/* Get final membership mask in the light sets, considering its inclusion and exclusion. */
uint64_t get_mask() const;
/* Bit masks of the emitter membership in the light sets. */
uint64_t included_sets_mask = 0;
uint64_t excluded_sets_mask = 0;
};
/* Packed information about emitter.
* Emitter is actually corresponding to a light linking collection on an object. */
class EmitterData {
@ -54,20 +69,127 @@ class EmitterData {
* The limitation is imposed by the fact that its identifier is converted to a bitmask. */
static constexpr int MAX_COLLECTION_ID = 63;
/* Bitmask which indicates the emitter belongs to all light sets. */
static constexpr uint64_t SET_MEMBERSHIP_ALL = ~uint64_t(0);
/* Get final emitter membership in the light sets, considering its inclusion and exclusion. */
uint64_t get_set_membership() const;
/* Mask of a light linking collection this emitter uses in its configuration.
* A single bit is set in this bitfield which corresponds to an identifier of a light linking
* collection in the scene. */
uint64_t collection_mask = 0;
/* Bit masks of the emitter membership in the light sets. */
uint64_t included_sets_mask = 0;
uint64_t excluded_sets_mask = 0;
/* Membership masks for the light and shadow linking. */
EmitterSetMembership light_membership;
EmitterSetMembership shadow_membership;
};
/* Helper class which deals with keeping per-emitter data. */
class EmitterDataMap {
using MapType = Map<const Collection *, EmitterData>;
public:
/* Returns true if there is no information about emitters at all. */
bool is_empty() const
{
return emitter_data_map_.is_empty();
}
/* Entirely clear the state, become ready for a new light linking relations build. */
void clear();
/* Ensure that the data exists for the given emitter.
* The emitter must be original and have light linking collection.
*
* Note that there is limited number of emitters possible within a scene, When this number is
* exceeded an error is printed and a nullptr is returned. */
EmitterData *ensure_data_if_possible(const Scene &scene, const Object &emitter);
/* Get emitter data for the given original or evaluated object.
* If the light linking is not configured for this emitted nullptr is returned. */
const EmitterData *get_data(const Object &emitter) const;
/* Returns true if the underlying data of the light linking emitter has been handled, and there
* is no need to handle the emitter.
* The emitter must be original object. */
bool can_skip_emitter(const Object &emitter) const;
/* Returns an iterator over all emitter data in the map. */
MapType::MutableValueIterator values()
{
return emitter_data_map_.values();
}
private:
/* Emitter-centric information: indexed by an original emitter object, contains accumulated
* information about emitter. */
MapType emitter_data_map_;
/* Next unique light linking collection ID. */
uint64_t next_collection_id_ = 0;
};
/* Common part of receiver (for light linking) and blocker (for shadow lining) data. */
class LinkingData {
public:
explicit LinkingData(const bool is_shadow) : is_shadow_(is_shadow) {}
/* Entirely clear the state, become ready for a new light linking relations build. */
void clear();
/* Link the given object with the given light linking state. */
void link_object(const EmitterData &emitter_data,
eCollectionLightLinkingState link_state,
const Object &object);
/* Compute unique sets of emitters used by receivers or blockers.
*
* This must be called at the end of depsgraph relations build after all emitters have been
* added, and before runtime data can be set as part of evaluation. */
void end_build(const Scene &scene, EmitterDataMap &emitter_data_map);
/* Get an unique index the given object is receiving light or casting shadow from.
* The object can either be original or evaluated.
*
* If the object is not linked to any emitter LightSet::DEFAULT_ID is returned. */
uint64_t get_light_set_for(const Object &object) const;
private:
/* Ensure that the light set exists for the given receiver/blocker object.
* The object must be original. */
LightSet &ensure_light_set_for(const Object &object);
/* Update the emitter light/shadow set membership after the final unique light set identifier
* is known.
* The light_set_mask consists of a single bit set corresponding to the light set index. */
void update_emitters_membership(EmitterDataMap &emitter_data_map,
const LightSet &light_set,
uint64_t light_set_mask);
/* Clear data which is only needed during the build. */
void clear_after_build();
/* Get light set membership information of the emitter data depending whether this linking
* data is a light or shadow linking. */
/* TODO(sergey): Check whether such per-update_emitters_membership() call is fast enough, or
* whether template specialization is preferred here. */
inline EmitterSetMembership &get_emitter_set_membership(EmitterData &emitter_data)
{
if (is_shadow_) {
return emitter_data.shadow_membership;
}
return emitter_data.light_membership;
}
bool is_shadow_ = false;
/* Receiver/blocker-centric view of light sets: indexed by an original receiver object, contains
* light set which defines from which emitters it receives light from or casts shadow when is lit
* ny.
*
* NOTE: Only available during build. */
Map<const Object *, LightSet> light_linked_sets_;
/* Map from an original receiver/blocker object: map to index of light set for this
* receiver/blocker. */
/* TODO(sergey): What is the generic term for receiver and blocker which is less generic than
* object? */
Map<const Object *, uint64_t> object_light_sets_;
};
} // namespace internal
@ -80,8 +202,9 @@ class EmitterData {
* This cache takes care of making it efficient to lookup emitter masks, emitters which affect
* given receiver and so on. */
class Cache {
using LightSet = internal::LightSet;
using EmitterData = internal::EmitterData;
using EmitterDataMap = internal::EmitterDataMap;
using LinkingData = internal::LinkingData;
public:
/* Entirely clear the cache.
@ -98,7 +221,7 @@ class Cache {
* object leads to an undefined behavior. */
void add_emitter(const Scene &scene, const Object &emitter);
/* Compute unique sets of emitters used by receivers.
/* Compute unique sets of emitters used by receivers and blockers.
*
* This must be called at the end of depsgraph relations build after all emitters have been
* added, and before runtime data can be set as part of evaluation. */
@ -108,53 +231,26 @@ class Cache {
void eval_runtime_data(Object &object_eval) const;
private:
/* Add receiver object with the given light linking configuration. */
void add_receiver_object(const Scene &scene,
const Object &emitter,
/* Add receiver or blocker object with the given light linking configuration.
*
* The term receiver here is meant in a wider meaning of it. For the light linking it is a
* receiver of light, but for the shadow linking is it actually a shadow caster. */
void add_receiver_object(const EmitterData &emitter_data,
const CollectionLightLinking &collection_light_linking,
const Object &receiver);
/* Ensure that the light set exists for the given receiver.
* The receiver must be original. */
LightSet &ensure_light_linked_set(const Object &receiver);
/* Returns true if the underlying data of the light linking emitter has been handled, and there
* is no need to handle the emitter.
* The emitter must be original object. */
bool can_skip_emitter(const Object &emitter) const;
/* Ensure that the data exists for the given emitter.
* The emitter must be original and have light linking collection.
*
* Note that there is limited number of emitters possible within a scene, When this number is
* exceeded an error is printed and a nullptr is returned. */
EmitterData *ensure_emitter_data_if_possible(const Scene &scene, const Object &emitter);
/* Get emitter data for the given original or evaluated object.
* If the light linking is not configured for this emitted nullptr is returned. */
const EmitterData *get_emitter_data(const Object &emitter) const;
/* Returns true if there is light linking configuration in the scene. */
bool has_light_linking() const
{
return !emitter_data_map_.is_empty();
}
/* Receiver-centric view of light sets: indexed by an original receiver object, contains light
* set which defines from which emitters it receives light from.
*
* NOTE: Only available during build. */
Map<const Object *, LightSet> light_linked_sets_;
/* Per-emitter light and shadow linking information. */
EmitterDataMap emitter_data_map_;
/* Emitter-centric information: indexed by an original emitter object, contains accumulated
* information about emitter. */
Map<const Collection *, EmitterData> emitter_data_map_;
/* Map from an original receiver object: map to index of light set for this receiver. */
Map<const Object *, uint64_t> receiver_light_sets_;
/* Next unique light linking collection ID. */
uint64_t next_collection_id_ = 0;
/* Light and shadow linking data. */
LinkingData light_linking_{false};
LinkingData shadow_linking_{true};
};
} // namespace blender::deg::light_linking

View File

@ -247,22 +247,40 @@ enum eObjectLineArt_Flags {
OBJECT_LRT_OWN_INTERSECTION_PRIORITY = (1 << 1),
};
/* Evaluated light linking state needed for the render engines integration. */
typedef struct LightLinkingRuntime {
/* For objects that emit light: a bitmask of light sets this emitter is part of.
/* For objects that emit light: a bitmask of light sets this emitter is part of for the light
* linking.
* A light set is a combination of emitters used by one or more receiver objects.
*
* If there is no light linking in the scene, this is assigned zero. If the emitter does
* not specify light linking, it is a member of all light sets.
*
* NOTE: There can only be 64 light sets in a scene. */
uint64_t set_membership;
uint64_t light_set_membership;
/* For receiver objects, the index of the light set from which this object receives light.
/* For objects that emit light: a bitmask of light sets this emitter is part of for the shadow
* linking.
* A light set is a combination of emitters from which a blocked object does not cast a shadow.
*
* If there is no shadow linking in the scene, this is assigned zero. If the emitter does
* not specify light linking, it is a member of all light sets.
*
* NOTE: There can only be 64 light sets in a scene. */
uint64_t shadow_set_membership;
/* For receiver objects: the index of the light set from which this object receives light.
*
* If there is no light linking in the scene, this is assigned zero. */
uint8_t receiver_set;
uint8_t receiver_light_set;
uint8_t _pad[7];
/* For blocker objects: the index of the light set from which this object casts shadow from.
*
* If there is no light shadow in the scene, this is assigned zero. */
uint8_t blocker_shadow_set;
uint8_t _pad[6];
} LightLinkingRuntime;
typedef struct LightLinking {