Cycles: build specialized light trees for light linking #107561

Merged
6 changed files with 332 additions and 32 deletions

View File

@ -465,7 +465,8 @@ ccl_device_inline float light_sample_mis_weight_forward_surface(KernelGlobals kg
uint prim_offset = kernel_data_fetch(object_prim_offset, sd->object);
uint triangle = kernel_data_fetch(triangle_to_tree, sd->prim - prim_offset + lookup_offset);
pdf *= light_tree_pdf(kg, ray_P, N, path_flag, sd->object, triangle);
pdf *= light_tree_pdf(
kg, ray_P, N, path_flag, sd->object, triangle, light_link_receiver_forward(kg, state));
}
else
#endif
@ -489,7 +490,13 @@ ccl_device_inline float light_sample_mis_weight_forward_lamp(KernelGlobals kg,
#ifdef __LIGHT_TREE__
if (kernel_data.integrator.use_light_tree) {
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
pdf *= light_tree_pdf(kg, P, N, path_flag, 0, kernel_data_fetch(light_to_tree, ls->lamp));
pdf *= light_tree_pdf(kg,
P,
N,
path_flag,
0,
kernel_data_fetch(light_to_tree, ls->lamp),
light_link_receiver_forward(kg, state));
}
else
#endif
@ -524,7 +531,8 @@ ccl_device_inline float light_sample_mis_weight_forward_background(KernelGlobals
if (kernel_data.integrator.use_light_tree) {
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
uint light = kernel_data_fetch(light_to_tree, kernel_data.background.light_index);
pdf *= light_tree_pdf(kg, ray_P, N, path_flag, 0, light);
pdf *= light_tree_pdf(
kg, ray_P, N, path_flag, 0, light, light_link_receiver_forward(kg, state));
}
else
#endif

View File

@ -656,6 +656,19 @@ ccl_device bool get_left_probability(KernelGlobals kg,
return true;
}
ccl_device int light_tree_root_node_index(KernelGlobals kg, const int object_receiver)
{
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_LINKING) {
const uint receiver_light_set =
(object_receiver != OBJECT_NONE) ?
kernel_data_fetch(objects, object_receiver).receiver_light_set :
0;
return kernel_data.light_link_sets[receiver_light_set].light_tree_root;
}
return 0;
}
template<bool in_volume_segment>
ccl_device_noinline bool light_tree_sample(KernelGlobals kg,
float randn,
@ -679,8 +692,8 @@ ccl_device_noinline bool light_tree_sample(KernelGlobals kg,
float pdf_leaf = 1.0f;
float pdf_selection = 1.0f;
int selected_emitter = -1;
int object = 0;
int node_index = 0; /* Root node. */
int object_emitter = 0;
int node_index = light_tree_root_node_index(kg, object_receiver);
float3 local_P = P;
@ -698,7 +711,7 @@ ccl_device_noinline bool light_tree_sample(KernelGlobals kg,
}
else {
/* Continue with the picked mesh light. */
object = kernel_data_fetch(light_tree_emitters, selected_emitter).mesh.object_id;
object_emitter = kernel_data_fetch(light_tree_emitters, selected_emitter).mesh.object_id;
continue;
}
}
@ -737,26 +750,31 @@ ccl_device_noinline bool light_tree_sample(KernelGlobals kg,
bounce,
path_flag,
selected_emitter,
object,
object_emitter,
pdf_selection,
ls);
}
/* We need to be able to find the probability of selecting a given light for MIS. */
ccl_device float light_tree_pdf(
KernelGlobals kg, float3 P, float3 N, const int path_flag, const int object, const uint target)
ccl_device float light_tree_pdf(KernelGlobals kg,
float3 P,
float3 N,
const int path_flag,
const int object_emitter,
const uint index_emitter,
const int object_receiver)
{
const bool has_transmission = (path_flag & PATH_RAY_MIS_HAD_TRANSMISSION);
ccl_global const KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
target);
index_emitter);
int root_index;
uint bit_trail, target_emitter;
if (is_triangle(kemitter)) {
/* If the target is an emissive triangle, first traverse the top level tree to find the mesh
* light emitter, then traverse the subtree. */
target_emitter = kernel_data_fetch(object_to_tree, object);
target_emitter = kernel_data_fetch(object_to_tree, object_emitter);
ccl_global const KernelLightTreeEmitter *kmesh = &kernel_data_fetch(light_tree_emitters,
target_emitter);
root_index = kmesh->mesh.node_id;
@ -770,11 +788,11 @@ ccl_device float light_tree_pdf(
else {
root_index = 0;
bit_trail = kemitter->bit_trail;
target_emitter = target;
target_emitter = index_emitter;
}
float pdf = 1.0f;
int node_index = 0;
int node_index = light_tree_root_node_index(kg, object_receiver);
/* Traverse the light tree until we reach the target leaf node. */
while (true) {
@ -813,11 +831,11 @@ ccl_device float light_tree_pdf(
if (root_index) {
/* Arrived at the mesh light. Continue with the subtree. */
float unused;
light_tree_to_local_space<false>(kg, object, P, N, unused);
light_tree_to_local_space<false>(kg, object_emitter, P, N, unused);
node_index = root_index;
root_index = 0;
target_emitter = target;
target_emitter = index_emitter;
bit_trail = kemitter->bit_trail;
continue;
}
@ -838,13 +856,14 @@ ccl_device float light_tree_pdf(
}
const bool go_left = (bit_trail & 1) == 0;
bit_trail >>= 1;
pdf *= go_left ? left_prob : (1.0f - left_prob);
node_index = go_left ? left_index : right_index;
pdf *= go_left ? left_prob : (1.0f - left_prob);
if (pdf == 0) {
return 0.0f;
}
bit_trail >>= kernel_data_fetch(light_tree_nodes, node_index).bit_shift;
}
}

View File

@ -1216,6 +1216,10 @@ typedef struct KernelBake {
} KernelBake;
static_assert_align(KernelBake, 16);
typedef struct KernelLightLinkSet {
uint light_tree_root;
} KernelLightLinkSet;
typedef struct KernelData {
/* Features and limits. */
uint kernel_features;
@ -1227,6 +1231,7 @@ typedef struct KernelData {
KernelCamera cam;
KernelBake bake;
KernelTables tables;
KernelLightLinkSet light_link_sets[LIGHT_LINK_SET_MAX];
/* Potentially specialized data members. */
#define KERNEL_STRUCT_BEGIN(name, parent) name parent;
@ -1428,6 +1433,12 @@ typedef struct KernelLightTreeNode {
/* Bit trail. */
uint bit_trail;
/* Bit shift for bit trail, to skip nodes for specialized trees. */
uint8_t bit_shift;
/* Padding. */
uint8_t pad[15];
} KernelLightTreeNode;
static_assert_align(KernelLightTreeNode, 16);

View File

@ -463,6 +463,7 @@ static void light_tree_node_copy_to_device(KernelLightTreeNode &knode,
knode.bcone.theta_e = node.measure.bcone.theta_e;
knode.bit_trail = node.bit_trail;
knode.bit_shift = 1;
knode.type = static_cast<LightTreeNodeType>(node.type);
if (node.is_leaf() || node.is_distant()) {
@ -616,6 +617,133 @@ static int light_tree_flatten(LightTreeFlatten &flatten,
return node_index;
}
static void light_tree_emitters_copy_and_flatten(LightTreeFlatten &flatten,
const LightTreeNode *node,
KernelLightTreeNode *knodes,
KernelLightTreeEmitter *kemitters,
int &next_node_index)
{
/* Convert only emitters to device representation. */
if (node->is_leaf() || node->is_distant()) {
light_tree_leaf_emitters_copy_and_flatten(flatten, *node, knodes, kemitters, next_node_index);
}
else {
assert(node->is_inner());
light_tree_emitters_copy_and_flatten(flatten,
node->get_inner().children[LightTree::left].get(),
knodes,
kemitters,
next_node_index);
light_tree_emitters_copy_and_flatten(flatten,
node->get_inner().children[LightTree::right].get(),
knodes,
kemitters,
next_node_index);
}
}
static std::pair<int, LightTreeMeasure> light_tree_specialize_nodes_flatten(
const LightTreeFlatten &flatten,
LightTreeNode *node,
const uint64_t light_link_mask,
const int depth,
vector<KernelLightTreeNode> &knodes,
int &next_node_index)
{
assert(!node->is_instance());
/* Convert inner nodes to device representation, specialized for light linking. */
int node_index, child_index = -1;
LightTreeNode new_node(LightTreeMeasure::empty, node->bit_trail);
if (depth == 0 && !(node->light_link.set_membership & light_link_mask)) {
/* Ensure there is always a root node. */
node_index = next_node_index++;
new_node.make_leaf(-1, 0);
}
else if (node->light_link.shareable && node->light_link.shared_node_index != -1) {
/* Share subtree already built for another light link set. */
return std::make_pair(node->light_link.shared_node_index, node->measure);
}
else if (node->is_leaf() || node->is_distant()) {
/* Specialize leaf node. */
node_index = next_node_index++;
int first_emitter = -1;
int num_emitters = 0;
for (int i = 0; i < node->get_leaf().num_emitters; i++) {
const LightTreeEmitter &emitter = flatten.emitters[node->get_leaf().first_emitter_index + i];
if (emitter.light_set_membership & light_link_mask) {
/* Assumes emitters are consecutive due to LighTree::sort_leaf. */
if (first_emitter == -1) {
first_emitter = node->get_leaf().first_emitter_index + i;
}
num_emitters++;
new_node.measure.add(emitter.measure);
}
}
assert(first_emitter != -1);
new_node.make_leaf(first_emitter, num_emitters);
}
else {
assert(node->is_inner());
assert(new_node.is_inner());
/* Specialize inner node. */
LightTreeNode *left_node = node->get_inner().children[LightTree::left].get();
LightTreeNode *right_node = node->get_inner().children[LightTree::right].get();
/* Skip nodes that have only one child. We have a single bit trail for each
* primitive, the bit shift is incremented to skip the bit for this node. */
LightTreeNode *only_node = nullptr;
if (!(left_node->light_link.set_membership & light_link_mask)) {
only_node = right_node;
}
else if (!(right_node->light_link.set_membership & light_link_mask)) {
only_node = left_node;
}
if (only_node) {
const auto [only_index, only_measure] = light_tree_specialize_nodes_flatten(
flatten, only_node, light_link_mask, depth + 1, knodes, next_node_index);
assert(only_index != -1);
knodes[only_index].bit_shift++;
return std::make_pair(only_index, only_measure);
}
/* Create inner node. */
node_index = next_node_index++;
const auto [left_index, left_measure] = light_tree_specialize_nodes_flatten(
flatten, left_node, light_link_mask, depth + 1, knodes, next_node_index);
const auto [right_index, right_measure] = light_tree_specialize_nodes_flatten(
flatten, right_node, light_link_mask, depth + 1, knodes, next_node_index);
new_node.measure = left_measure;
new_node.measure.add(right_measure);
/* Nodes are stored in depth first order so that the left child node
* immediately follows the parent, and only the right child index needs
* to be stored. */
child_index = right_index;
}
/* Convert to kernel node. */
if (knodes.size() <= node_index) {
knodes.resize(node_index + 1);
}
light_tree_node_copy_to_device(knodes[node_index], new_node, child_index);
if (node->light_link.shareable) {
node->light_link.shared_node_index = node_index;
}
return std::make_pair(node_index, new_node.measure);
}
void LightManager::device_update_tree(Device *,
DeviceScene *dscene,
Scene *scene,
@ -649,23 +777,67 @@ void LightManager::device_update_tree(Device *,
flatten.mesh_array = dscene->object_to_tree.alloc(scene->objects.size());
flatten.triangle_array = dscene->triangle_to_tree.alloc(light_tree.num_triangles);
/* Allocate emitters and nodes. */
/* Allocate emitters */
const size_t num_emitters = light_tree.num_emitters();
KernelLightTreeEmitter *kemitters = dscene->light_tree_emitters.alloc(num_emitters);
KernelLightTreeNode *knodes = dscene->light_tree_nodes.alloc(light_tree.num_nodes);
/* Update integrator state. */
kintegrator->use_direct_light = num_emitters > 0;
/* Copy nodes and emitters. */
if (root) {
int next_node_index = 0;
light_tree_flatten(flatten, root, knodes, kemitters, next_node_index);
}
/* Test if light linking is used. */
const bool use_light_linking = root && root->light_link.set_membership != 0;
KernelLightLinkSet *klight_link_sets = dscene->data.light_link_sets;
memset(klight_link_sets, 0, sizeof(dscene->data.light_link_sets));
VLOG_INFO << "Use light tree with " << num_emitters << " emitters and " << light_tree.num_nodes
<< " nodes.";
if (!use_light_linking) {
/* Regular light tree without linking. */
KernelLightTreeNode *knodes = dscene->light_tree_nodes.alloc(light_tree.num_nodes);
if (root) {
int next_node_index = 0;
light_tree_flatten(flatten, root, knodes, kemitters, next_node_index);
}
}
else {
int next_node_index = 0;
vector<KernelLightTreeNode> light_link_nodes;
/* Write primitives, and any subtrees for instances. */
if (root) {
/* Reserve enough size of all instance subtrees, then shrink back to
* actual number of nodes used. */
light_link_nodes.resize(light_tree.num_nodes);
light_tree_emitters_copy_and_flatten(
flatten, root, light_link_nodes.data(), kemitters, next_node_index);
light_link_nodes.resize(next_node_index);
}
/* Specialized light trees for linking. */
for (uint64_t tree_index = 0; tree_index < LIGHT_LINK_SET_MAX; tree_index++) {
const uint64_t tree_mask = uint64_t(1) << tree_index;
if (!(light_tree.light_link_receiver_used & tree_mask)) {
continue;
}
klight_link_sets[tree_index].light_tree_root = next_node_index;
if (root) {
light_tree_specialize_nodes_flatten(
flatten, root, tree_mask, 0, light_link_nodes, next_node_index);
}
}
/* Allocate and copy nodes into device array. */
KernelLightTreeNode *knodes = dscene->light_tree_nodes.alloc(light_link_nodes.size());
memcpy(knodes, light_link_nodes.data(), light_link_nodes.size() * sizeof(*knodes));
VLOG_INFO << "Specialized light tree for light linking, with "
<< light_link_nodes.size() - light_tree.num_nodes << " additional nodes.";
}
/* Copy arrays to device. */
dscene->light_tree_nodes.copy_to_device();
dscene->light_tree_emitters.copy_to_device();

View File

@ -77,6 +77,7 @@ OrientationBounds merge(const OrientationBounds &cone_a, const OrientationBounds
LightTreeEmitter::LightTreeEmitter(Object *object, int object_id) : object_id(object_id)
{
centroid = object->bounds.center();
light_set_membership = object->get_light_set_membership();
}
LightTreeEmitter::LightTreeEmitter(Scene *scene,
@ -138,6 +139,8 @@ LightTreeEmitter::LightTreeEmitter(Scene *scene,
for (int i = 0; i < 3; i++) {
measure.bbox.grow(vertices[i]);
}
light_set_membership = object->get_light_set_membership();
}
else {
assert(is_light());
@ -216,6 +219,20 @@ LightTreeEmitter::LightTreeEmitter(Scene *scene,
/* Use absolute value of energy so lights with negative strength are properly supported in the
* light tree. */
measure.energy = fabsf(average(strength));
light_set_membership = lamp->get_light_set_membership();
}
}
static void sort_leaf(const int start, const int end, LightTreeEmitter *emitters)
{
/* Sort primitive by light link mask so that specialized trees can use a subset of these. */
if (end > start) {
std::sort(emitters + start,
emitters + end,
[](const LightTreeEmitter &a, const LightTreeEmitter &b) {
return a.light_set_membership < b.light_set_membership;
});
}
}
@ -279,6 +296,8 @@ LightTree::LightTree(Scene *scene,
return;
}
light_link_receiver_used |= (uint64_t(1) << object->get_receiver_light_set());
if (!object->usable_as_light()) {
object_id++;
continue;
@ -390,7 +409,15 @@ LightTreeNode *LightTree::build(Scene *scene, DeviceScene *dscene)
for (int i = 0; i < num_distant_lights; i++) {
root_->get_inner().children[right]->add(distant_lights_[i]);
}
sort_leaf(0, num_distant_lights, distant_lights_.data());
root_->get_inner().children[right]->make_distant(num_local_lights, num_distant_lights);
root_->measure = root_->get_inner().children[left]->measure +
root_->get_inner().children[right]->measure;
root_->light_link = root_->get_inner().children[left]->light_link +
root_->get_inner().children[right]->light_link;
std::move(distant_lights_.begin(), distant_lights_.end(), std::back_inserter(emitters_));
return root_.get();
@ -421,7 +448,7 @@ void LightTree::recursive_build(const Child child,
/* Find the best place to split the emitters into 2 nodes.
* If the best split cost is no better than making a leaf node, make a leaf instead. */
int split_dim = -1, middle;
if (should_split(emitters, start, middle, end, node->measure, split_dim)) {
if (should_split(emitters, start, middle, end, node->measure, node->light_link, split_dim)) {
if (split_dim != -1) {
/* Partition the emitters between start and end based on the centroids. */
@ -453,6 +480,7 @@ void LightTree::recursive_build(const Child child,
}
}
else {
sort_leaf(start, end, emitters);
node->make_leaf(start, end - start);
}
}
@ -462,13 +490,15 @@ bool LightTree::should_split(LightTreeEmitter *emitters,
int &middle,
const int end,
LightTreeMeasure &measure,
LightTreeLightLink &light_link,
int &split_dim)
{
const int num_emitters = end - start;
if (num_emitters < 2) {
if (num_emitters) {
/* Do not try to split if there is only one emitter. */
measure = (emitters + start)->measure;
measure = emitters[start].measure;
light_link = LightTreeLightLink(emitters[start].light_set_membership);
}
return false;
}
@ -518,6 +548,7 @@ bool LightTree::should_split(LightTreeEmitter *emitters,
if (dim == 0) {
/* Calculate node measure by summing up the bucket measure. */
measure = left_buckets.back().measure + buckets.back().measure;
light_link = left_buckets.back().light_link + buckets.back().light_link;
/* Degenerate case with co-located emitters. */
if (is_zero(centroid_bbox.size())) {
@ -563,7 +594,14 @@ __forceinline LightTreeMeasure operator+(const LightTreeMeasure &a, const LightT
LightTreeBucket operator+(const LightTreeBucket &a, const LightTreeBucket &b)
{
return LightTreeBucket(a.measure + b.measure, a.count + b.count);
return LightTreeBucket(a.measure + b.measure, a.light_link + b.light_link, a.count + b.count);
}
LightTreeLightLink operator+(const LightTreeLightLink &a, const LightTreeLightLink &b)
{
LightTreeLightLink c(a);
c.add(b);
return c;
}
CCL_NAMESPACE_END

View File

@ -127,6 +127,48 @@ LightTreeMeasure operator+(const LightTreeMeasure &a, const LightTreeMeasure &b)
struct LightTreeNode;
/* Light Linking. */
struct LightTreeLightLink {
/* Bitmask for membership of primitives in this node. */
uint64_t set_membership = 0;
/* When all primitives below this node have identical light set membership, this
* part of the light tree can be shared between specialized trees. */
bool shareable = true;
int shared_node_index = -1;
LightTreeLightLink() = default;
LightTreeLightLink(const uint64_t set_membership) : set_membership(set_membership) {}
void add(const uint64_t prim_set_membership)
{
if (set_membership == 0) {
set_membership = prim_set_membership;
}
else if (prim_set_membership != set_membership) {
set_membership |= prim_set_membership;
shareable = false;
}
}
void add(const LightTreeLightLink &other)
{
if (set_membership == 0) {
set_membership = other.set_membership;
shareable = other.shareable;
}
else if (other.set_membership != set_membership) {
set_membership |= other.set_membership;
shareable = false;
}
else if (!other.shareable) {
shareable = false;
}
}
};
LightTreeLightLink operator+(const LightTreeLightLink &a, const LightTreeLightLink &b);
/* Light Tree Emitter
* An emitter is a built-in light, an emissive mesh, or an emissive triangle. */
struct LightTreeEmitter {
@ -140,6 +182,7 @@ struct LightTreeEmitter {
int object_id;
float3 centroid;
uint64_t light_set_membership;
LightTreeMeasure measure;
@ -166,19 +209,23 @@ struct LightTreeEmitter {
* Struct used to determine splitting costs in the light BVH. */
struct LightTreeBucket {
LightTreeMeasure measure;
LightTreeLightLink light_link;
int count = 0;
static const int num_buckets = 12;
LightTreeBucket() = default;
LightTreeBucket(const LightTreeMeasure &measure, const int &count)
: measure(measure), count(count)
LightTreeBucket(const LightTreeMeasure &measure,
const LightTreeLightLink &light_link,
const int &count)
: measure(measure), light_link(light_link), count(count)
{
}
void add(const LightTreeEmitter &emitter)
{
measure.add(emitter.measure);
light_link.add(emitter.light_set_membership);
count++;
}
};
@ -188,6 +235,7 @@ LightTreeBucket operator+(const LightTreeBucket &a, const LightTreeBucket &b);
/* Light Tree Node */
struct LightTreeNode {
LightTreeMeasure measure;
LightTreeLightLink light_link;
uint bit_trail;
int object_id;
@ -256,7 +304,7 @@ struct LightTreeNode {
return std::get<Instance>(variant_type);
}
void make_leaf(const int &first_emitter_index, const int &num_emitters)
void make_leaf(const int first_emitter_index, const int num_emitters)
{
variant_type = Leaf();
Leaf &leaf = get_leaf();
@ -266,7 +314,7 @@ struct LightTreeNode {
type = LIGHT_TREE_LEAF;
}
void make_distant(const int &first_emitter_index, const int &num_emitters)
void make_distant(const int first_emitter_index, const int num_emitters)
{
variant_type = Leaf();
Leaf &leaf = get_leaf();
@ -276,7 +324,7 @@ struct LightTreeNode {
type = LIGHT_TREE_DISTANT;
}
void make_instance(LightTreeNode *reference, const int &object_id)
void make_instance(LightTreeNode *reference, const int object_id)
{
variant_type = Instance();
Instance &instance = get_instance();
@ -340,6 +388,9 @@ class LightTree {
std::atomic<int> num_nodes = 0;
size_t num_triangles = 0;
/* Bitmask of receiver light sets used. Default set is always used. */
uint64_t light_link_receiver_used = 1;
/* An inner node itself or its left and right child. */
enum Child {
self = -1,
@ -388,6 +439,7 @@ class LightTree {
int &middle,
const int end,
LightTreeMeasure &measure,
LightTreeLightLink &light_link,
int &split_dim);
/* Check whether the light tree can use this triangle as light-emissive. */