Plotting happens from the given root node into a graphviz file. Supports plotting from both scene level LightTreeNode and the kernel level KernelLightTreeNode. An external graphviz command is to be used to convert generated file to an image. Pull Request: https://projects.blender.org/blender/blender/pulls/134738
331 lines
11 KiB
C++
331 lines
11 KiB
C++
/* SPDX-FileCopyrightText: 2011-2025 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
#include "scene/light_tree_debug.h"
|
|
|
|
#include "scene/light.h"
|
|
#include "scene/light_tree.h"
|
|
#include "scene/object.h"
|
|
#include "scene/scene.h"
|
|
#include "util/path.h"
|
|
#include "util/string.h"
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
static string get_node_id(const LightTreeNode &node)
|
|
{
|
|
return string_printf("node@%p", &node);
|
|
}
|
|
|
|
static string get_emitter_id(const LightTreeEmitter &emitter)
|
|
{
|
|
return string_printf("emitter@%p", &emitter);
|
|
}
|
|
|
|
static string set_membership_str(const uint64_t set_membership)
|
|
{
|
|
if (set_membership == ~uint64_t(0)) {
|
|
return "ALL";
|
|
}
|
|
return std::to_string(set_membership);
|
|
}
|
|
|
|
static void recursive_print_node(FILE *file, const LightTreeNode &node)
|
|
{
|
|
int field = 0;
|
|
|
|
string label = string_printf("<f%d> node @%p", field++, &node);
|
|
if (node.is_instance()) {
|
|
label += string_printf("| <f%d> instance", field++);
|
|
}
|
|
if (node.is_leaf()) {
|
|
label += string_printf("| <f%d> leaf", field++);
|
|
}
|
|
if (node.is_inner()) {
|
|
label += string_printf("| <f%d> inner", field++);
|
|
}
|
|
if (node.is_distant()) {
|
|
label += string_printf("| <f%d> distant", field++);
|
|
}
|
|
|
|
if (node.light_link.set_membership == ~uint64_t(0)) {
|
|
label += string_printf("| <f%d> set membership:ALL", field++);
|
|
}
|
|
else {
|
|
label += string_printf("| <f%d> set membership:%s",
|
|
field++,
|
|
set_membership_str(node.light_link.set_membership).c_str());
|
|
}
|
|
|
|
label += string_printf(
|
|
"| <f%d> shareable:%s", field++, node.light_link.shareable ? "True" : "False");
|
|
|
|
if (node.is_inner()) {
|
|
label += string_printf("| <left> left");
|
|
label += string_printf("| <right> right");
|
|
}
|
|
else if (node.is_leaf()) {
|
|
label += string_printf("| <emitters> emitters");
|
|
}
|
|
|
|
fprintf(file, "\"%s\" [\n", get_node_id(node).c_str());
|
|
fprintf(file, " label = \"%s\"\n", label.c_str());
|
|
fprintf(file, " shape = \"record\"\n");
|
|
fprintf(file, "];\n");
|
|
|
|
if (node.is_inner()) {
|
|
const LightTreeNode &left_node = *node.get_inner().children[LightTree::left].get();
|
|
const LightTreeNode &right_node = *node.get_inner().children[LightTree::right].get();
|
|
|
|
recursive_print_node(file, left_node);
|
|
recursive_print_node(file, right_node);
|
|
}
|
|
}
|
|
|
|
static void print_emitters(FILE *file, const Scene &scene, const LightTree &tree)
|
|
{
|
|
const size_t num_emitters = tree.num_emitters();
|
|
const LightTreeEmitter *emitters = tree.get_emitters();
|
|
for (size_t i = 0; i < num_emitters; ++i) {
|
|
const LightTreeEmitter &emitter = emitters[i];
|
|
|
|
int field = 0;
|
|
|
|
string label = string_printf("<f%d> emitter %s", field++, std::to_string(i).c_str());
|
|
|
|
/* Emitter details (type, object or light name). */
|
|
if (emitter.is_light()) {
|
|
const Light &lamp = *scene.lights[emitter.object_id];
|
|
label += string_printf("|<f%d> light", field++);
|
|
label += string_printf("|<f%d> %s", field++, lamp.name.c_str());
|
|
}
|
|
else if (emitter.is_triangle()) {
|
|
const Object &object = *scene.objects[emitter.object_id];
|
|
label += string_printf("|<f%d> triangle", field++);
|
|
label += string_printf("|<f%d> %s", field++, object.name.c_str());
|
|
}
|
|
else if (emitter.is_mesh()) {
|
|
const Object &object = *scene.objects[emitter.object_id];
|
|
label += string_printf("|<f%d> mesh", field++);
|
|
label += string_printf("|<f%d> %s", field++, object.name.c_str());
|
|
}
|
|
|
|
/* Light linking. */
|
|
if (emitter.light_set_membership == ~uint64_t(0)) {
|
|
label += string_printf("|<f%d> set membership:ALL", field++);
|
|
}
|
|
else {
|
|
label += string_printf("|<f%d> set membership:%s",
|
|
field++,
|
|
set_membership_str(emitter.light_set_membership).c_str());
|
|
}
|
|
|
|
/* Bounding box. */
|
|
label += string_printf("|<f%d> bbox.min (%f %f %f)",
|
|
field++,
|
|
double(emitter.measure.bbox.min.x),
|
|
double(emitter.measure.bbox.min.y),
|
|
double(emitter.measure.bbox.min.z));
|
|
label += string_printf("|<f%d> bbox.max (%f %f %f)",
|
|
field++,
|
|
double(emitter.measure.bbox.max.x),
|
|
double(emitter.measure.bbox.max.y),
|
|
double(emitter.measure.bbox.max.z));
|
|
|
|
/* Orientation bounds. */
|
|
label += string_printf("|<f%d> bcone.axis (%f %f %f)",
|
|
field++,
|
|
double(emitter.measure.bcone.axis.x),
|
|
double(emitter.measure.bcone.axis.y),
|
|
double(emitter.measure.bcone.axis.z));
|
|
label += string_printf("|<f%d> theta_o %f, theta_e %f",
|
|
field++,
|
|
double(emitter.measure.bcone.theta_o),
|
|
double(emitter.measure.bcone.theta_e));
|
|
|
|
/* Print node to the file. */
|
|
fprintf(file, "\"%s\" [\n", get_emitter_id(emitter).c_str());
|
|
fprintf(file, " label = \"%s\"\n", label.c_str());
|
|
fprintf(file, " shape = \"record\"\n");
|
|
fprintf(file, "];\n");
|
|
}
|
|
}
|
|
|
|
static void recursive_print_node_relations(FILE *file,
|
|
const LightTree &tree,
|
|
const LightTreeNode &node,
|
|
int &relation_id)
|
|
{
|
|
const string from_node_id = get_node_id(node);
|
|
|
|
if (node.is_leaf() || node.is_distant()) {
|
|
const LightTreeEmitter *emitters = tree.get_emitters();
|
|
for (int i = 0; i < node.get_leaf().num_emitters; i++) {
|
|
const LightTreeEmitter &emitter = emitters[node.get_leaf().first_emitter_index + i];
|
|
fprintf(file,
|
|
"\"%s\":<emitters> -> \"%s\":f0 [ id = %d ];\n",
|
|
from_node_id.c_str(),
|
|
get_emitter_id(emitter).c_str(),
|
|
relation_id++);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!node.is_inner()) {
|
|
return;
|
|
}
|
|
|
|
const LightTreeNode &left_node = *node.get_inner().children[LightTree::left].get();
|
|
const LightTreeNode &right_node = *node.get_inner().children[LightTree::right].get();
|
|
|
|
const string left_node_id = get_node_id(left_node);
|
|
const string right_node_id = get_node_id(right_node);
|
|
|
|
fprintf(file,
|
|
"\"%s\":left -> \"%s\":f0 [ id = %d ];\n",
|
|
from_node_id.c_str(),
|
|
left_node_id.c_str(),
|
|
relation_id++);
|
|
|
|
fprintf(file,
|
|
"\"%s\":right -> \"%s\":f0 [ id = %d ];\n",
|
|
from_node_id.c_str(),
|
|
right_node_id.c_str(),
|
|
relation_id++);
|
|
|
|
recursive_print_node_relations(file, tree, left_node, relation_id);
|
|
recursive_print_node_relations(file, tree, right_node, relation_id);
|
|
}
|
|
|
|
void light_tree_plot_to_file(const Scene &scene,
|
|
const LightTree &tree,
|
|
const LightTreeNode &root_node,
|
|
const string &filename)
|
|
{
|
|
FILE *file = path_fopen(filename, "w");
|
|
if (!file) {
|
|
return;
|
|
}
|
|
|
|
int relation_id = 0;
|
|
|
|
fprintf(file, "digraph g {\n");
|
|
fprintf(file, "graph [\n");
|
|
fprintf(file, " rankdir = \"LR\"\n");
|
|
fprintf(file, "];\n");
|
|
recursive_print_node(file, root_node);
|
|
print_emitters(file, scene, tree);
|
|
recursive_print_node_relations(file, tree, root_node, relation_id);
|
|
fprintf(file, "}\n");
|
|
|
|
fclose(file);
|
|
}
|
|
|
|
static string get_knode_id(const KernelLightTreeNode &knode)
|
|
{
|
|
return string_printf("knode@%p", &knode);
|
|
}
|
|
|
|
static void recursive_print_knode(FILE *file,
|
|
const uint knode_index,
|
|
const KernelLightTreeNode *knodes,
|
|
int &relation_id)
|
|
{
|
|
const KernelLightTreeNode &knode = knodes[knode_index];
|
|
|
|
/* The distant node is also considered o leaf node. */
|
|
const bool is_leaf = knode.type >= LIGHT_TREE_LEAF;
|
|
const bool is_inner = !is_leaf;
|
|
|
|
assert(!is_inner || knode.type != LIGHT_TREE_INSTANCE);
|
|
|
|
int field = 0;
|
|
|
|
string label = string_printf("<f%d> knode %u", field++, knode_index);
|
|
|
|
/* Bounding box. */
|
|
label += string_printf("|<f%d> min (%f %f %f)",
|
|
field++,
|
|
double(knode.bbox.min.x),
|
|
double(knode.bbox.min.y),
|
|
double(knode.bbox.min.z));
|
|
label += string_printf("|<f%d> max (%f %f %f)",
|
|
field++,
|
|
double(knode.bbox.max.x),
|
|
double(knode.bbox.max.y),
|
|
double(knode.bbox.max.z));
|
|
|
|
/* Orientation bounds. */
|
|
label += string_printf("|<f%d> bcone.axis (%f %f %f)",
|
|
field++,
|
|
double(knode.bcone.axis.x),
|
|
double(knode.bcone.axis.y),
|
|
double(knode.bcone.axis.z));
|
|
label += string_printf("|<f%d> theta_o %f, theta_e %f",
|
|
field++,
|
|
double(knode.bcone.theta_o),
|
|
double(knode.bcone.theta_e));
|
|
|
|
label += string_printf("|<f%d> energy %f", field++, knode.energy);
|
|
|
|
if (is_leaf) {
|
|
label += string_printf("|<f%d> first emitter %d", field++, knode.leaf.first_emitter);
|
|
label += string_printf("|<f%d> num emitters %d", field++, knode.num_emitters);
|
|
}
|
|
|
|
label += string_printf("|<f%d> bit trail %u", field++, knode.bit_trail);
|
|
label += string_printf("|<f%d> bit skip %d", field++, int(knode.bit_skip));
|
|
|
|
if (is_inner) {
|
|
label += string_printf("| <left> left");
|
|
label += string_printf("| <right> right");
|
|
}
|
|
|
|
const string knode_id = get_knode_id(knode);
|
|
|
|
/* Print node to the file. */
|
|
fprintf(file, "\"%s\" [\n", knode_id.c_str());
|
|
fprintf(file, " label = \"%s\"\n", label.c_str());
|
|
fprintf(file, " shape = \"record\"\n");
|
|
fprintf(file, "];\n");
|
|
|
|
/* Print relations. */
|
|
if (is_inner) {
|
|
recursive_print_knode(file, knode.inner.left_child, knodes, relation_id);
|
|
recursive_print_knode(file, knode.inner.right_child, knodes, relation_id);
|
|
|
|
fprintf(file,
|
|
"\"%s\":left -> \"%s\":f0 [ id = %d ];\n",
|
|
knode_id.c_str(),
|
|
get_knode_id(knodes[knode.inner.left_child]).c_str(),
|
|
relation_id++);
|
|
fprintf(file,
|
|
"\"%s\":right -> \"%s\":f0 [ id = %d ];\n",
|
|
knode_id.c_str(),
|
|
get_knode_id(knodes[knode.inner.right_child]).c_str(),
|
|
relation_id++);
|
|
}
|
|
}
|
|
|
|
void klight_tree_plot_to_file(uint root, const KernelLightTreeNode *knodes, const string &filename)
|
|
{
|
|
FILE *file = path_fopen(filename, "w");
|
|
if (!file) {
|
|
return;
|
|
}
|
|
|
|
int relation_id = 0;
|
|
|
|
fprintf(file, "digraph g {\n");
|
|
fprintf(file, "graph [\n");
|
|
fprintf(file, " rankdir = \"LR\"\n");
|
|
fprintf(file, "];\n");
|
|
recursive_print_knode(file, root, knodes, relation_id);
|
|
fprintf(file, "}\n");
|
|
|
|
fclose(file);
|
|
}
|
|
|
|
CCL_NAMESPACE_END
|