Compositor: Support socket value inspection

This patch adds support for socket value inspection in the compositor.
This displays the following information:

- If the image has a display window, the data and display windows are
  displayed. If not, the image resolution is displayed.
- If the image is transformed, its translation, rotation, and scale are
  displayed. If not, it will say Identity.
- The sampling options are displayed, including interpolation and
  sampling modes.
- The precision of the data, Full or Half.

Pixel nodes are not yet supported in this PR.

Pull Request: https://projects.blender.org/blender/blender/pulls/158546
This commit is contained in:
Omar Emara 2026-05-14 10:40:57 +02:00 • committed by Omar Emara
parent 17e76a69d4
commit ffd88022c9
13 changed files with 256 additions and 19 deletions

View file

@ -8,6 +8,7 @@
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_string_ref.hh"
#include "GPU_texture.hh"
@ -204,6 +205,8 @@ class Domain {
bool operator==(const Domain &a, const Domain &b);
bool operator!=(const Domain &a, const Domain &b);
StringRefNull to_string(const Interpolation &interpolation);
StringRefNull to_string(const Extension &extension);
GPUSamplerExtendMode map_extension_mode_to_extend_mode(const Extension &mode);
} // namespace blender::compositor

View file

@ -63,8 +63,8 @@ class NodeOperation : public Operation {
void set_needs_node_previews(const bool needed);
protected:
/* Compute a node preview using the result returned from the get_preview_result method. */
void compute_preview() override;
/* Log the values for the inputs and outputs of the node as well as its image preview. */
void log_data() override;
/* Returns a reference to the node that this operation represents. */
const bNode &node() const;

View file

@ -114,9 +114,8 @@ class Operation {
* output results. */
virtual void execute() = 0;
/* Compute and set a preview of the operation if needed. This method defaults to an empty
* implementation and should be implemented by operations which can have previews. */
virtual void compute_preview();
/* Log the data of the operation into the context logger. */
virtual void log_data();
/* Add the given result to the results_ map identified by the given output identifier. This
* should be called during operation construction for all outputs. The provided result shouldn't

View file

@ -107,12 +107,12 @@ class PixelOperation : public Operation {
const Schedule &schedule,
const ComputeContext &compute_context);
/* Compute a node preview for all nodes in the pixel operations if the node requires a preview.
/* Log a node preview for all nodes in the pixel operations if the node requires a preview.
*
* Previews are computed from results that are populated for outputs that are used to compute
* previews even if they are internally linked, and those outputs are stored and tracked in the
* preview_outputs_ vector set, see the populate_results_for_node method for more information. */
void compute_preview() override;
void log_data() override;
/* Get the identifier of the operation output corresponding to the given output socket. This is
* called by the compiler to identify the operation output that provides the result for an input

View file

@ -23,6 +23,7 @@
#include "BLI_math_vector.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_memory_utils.hh"
#include "BLI_string_ref.hh"
#include "GPU_shader.hh"
#include "GPU_texture.hh"
@ -482,6 +483,8 @@ class Result {
int64_t get_pixel_index(const int2 &texel) const;
};
StringRefNull to_string(const ResultPrecision &precision);
/* -------------------------------------------------------------------- */
/* Inline Methods.
*/

View file

@ -9,6 +9,8 @@
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector_types.hh"
#include "BLT_translation.hh"
#include "GPU_texture.hh"
#include "COM_domain.hh"
@ -107,6 +109,38 @@ bool operator!=(const Domain &a, const Domain &b)
return !(a == b);
}
StringRefNull to_string(const Interpolation &interpolation)
{
switch (interpolation) {
case Interpolation::Nearest:
return N_("Nearest");
case Interpolation::Bilinear:
return N_("Bilinear");
case Interpolation::Bicubic:
return N_("Bicubic");
case Interpolation::Anisotropic:
return N_("Anisotropic");
}
BLI_assert_unreachable();
return "None";
}
StringRefNull to_string(const Extension &extension)
{
switch (extension) {
case Extension::Extend:
return N_("Extend");
case Extension::Repeat:
return N_("Repeat");
case Extension::Clip:
return N_("Clip");
}
BLI_assert_unreachable();
return "None";
}
GPUSamplerExtendMode map_extension_mode_to_extend_mode(const Extension &mode)
{
switch (mode) {

View file

@ -131,21 +131,81 @@ void NodeOperation::set_needs_node_previews(const bool needed)
needs_node_previews_ = needed;
}
void NodeOperation::compute_preview()
static destruct_ptr<nodes::eval_log::ImageInfoLog> get_image_info_log(LinearAllocator<> *allocator,
const Result &result)
{
if (!needs_node_previews_ || !is_node_preview_needed(this->node())) {
const Domain &domain = result.domain();
return allocator->construct<nodes::eval_log::ImageInfoLog>(
domain.data_size,
domain.display_size,
domain.data_offset,
domain.transformation,
to_string(domain.realization_options.interpolation),
to_string(domain.realization_options.extension_x),
to_string(domain.realization_options.extension_y),
to_string(result.precision()));
}
void NodeOperation::log_data()
{
nodes::eval_log::NodesEvalLog *log = this->context().nodes_evaluation_log();
if (!log) {
return;
}
nodes::eval_log::NodeTreeLogger &tree_logger = log->get_local_tree_logger(*compute_context_);
const Result *result = this->get_preview_result();
if (!result || result->is_single_value()) {
return;
/* Log input values. */
for (const bNodeSocket *input_socket : this->node().input_sockets()) {
if (!is_socket_available(input_socket)) {
continue;
}
const Result &input = this->get_input(input_socket->identifier);
if (!input_socket->is_logically_linked()) {
continue;
}
if (input.is_single_value()) {
tree_logger.log_value(this->node(), *input_socket, input.single_value());
continue;
}
tree_logger.input_socket_values.append(*tree_logger.allocator,
{node_.identifier,
input_socket->index(),
get_image_info_log(tree_logger.allocator, input)});
}
ImBuf *preview = compositor::compute_preview(this->context(), *result);
nodes::eval_log::NodeTreeLogger &tree_logger =
this->context().nodes_evaluation_log()->get_local_tree_logger(this->get_compute_context());
tree_logger.node_image_previews.append(*tree_logger.allocator, {node_.identifier, preview});
/* Log output values. */
for (const bNodeSocket *output_socket : this->node().output_sockets()) {
if (!is_socket_available(output_socket)) {
continue;
}
const Result &result = this->get_result(output_socket->identifier);
if (!result.is_allocated()) {
continue;
}
if (result.is_single_value()) {
tree_logger.log_value(this->node(), *output_socket, result.single_value());
continue;
}
tree_logger.output_socket_values.append(*tree_logger.allocator,
{node_.identifier,
output_socket->index(),
get_image_info_log(tree_logger.allocator, result)});
}
/* Log node preview. */
if (needs_node_previews_ && is_node_preview_needed(this->node())) {
const Result *result = this->get_preview_result();
if (result && !result->is_single_value()) {
ImBuf *preview = compositor::compute_preview(this->context(), *result);
tree_logger.node_image_previews.append(*tree_logger.allocator, {node_.identifier, preview});
}
}
}
const bNode &NodeOperation::node() const

View file

@ -27,7 +27,7 @@ void Operation::evaluate()
{
this->evaluate_input_processors();
this->execute();
this->compute_preview();
this->log_data();
this->release_inputs();
this->context().evaluate_operation_post();
}
@ -112,7 +112,7 @@ void Operation::evaluate_input_processors()
}
}
void Operation::compute_preview() {};
void Operation::log_data() {};
void Operation::populate_result(StringRef identifier, Result result)
{

View file

@ -37,7 +37,7 @@ PixelOperation::PixelOperation(Context &context,
{
}
void PixelOperation::compute_preview()
void PixelOperation::log_data()
{
for (const bNodeSocket *output : preview_outputs_) {
Result &result = this->get_result(get_output_identifier_from_output_socket(*output));

View file

@ -16,6 +16,8 @@
#include "BLI_math_quaternion_types.hh"
#include "BLI_math_vector_types.hh"
#include "BLT_translation.hh"
#include "GPU_shader.hh"
#include "GPU_state.hh"
#include "GPU_texture.hh"
@ -1169,4 +1171,17 @@ void Result::allocate_data(const int2 size,
}
}
StringRefNull to_string(const ResultPrecision &precision)
{
switch (precision) {
case ResultPrecision::Full:
return N_("Full");
case ResultPrecision::Half:
return N_("Half");
}
BLI_assert_unreachable();
return "None";
}
} // namespace blender::compositor

View file

@ -12,7 +12,11 @@
#include "BKE_node_runtime.hh"
#include "BKE_type_conversions.hh"
#include "BLI_math_angle_types.hh"
#include "BLI_math_base.hh"
#include "BLI_math_euler.hh"
#include "BLI_math_matrix.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_string.h"
#include "BLT_translation.hh"
@ -352,6 +356,9 @@ class SocketTooltipBuilder {
else if (const auto *list_log = dynamic_cast<const eval_log::ListInfoLog *>(&value_log)) {
this->build_tooltip_value_list_log(*list_log);
}
else if (const auto *image_log = dynamic_cast<const eval_log::ImageInfoLog *>(&value_log)) {
this->build_tooltip_value_image_log(*image_log);
}
}
void build_tooltip_value_and_type_oneline(const StringRef value, const StringRef type)
@ -773,6 +780,76 @@ class SocketTooltipBuilder {
this->add_text_field_mono(TIP_("Type: List"));
}
void build_tooltip_value_image_log(const eval_log::ImageInfoLog &image_log)
{
const bool has_display_window = image_log.data_size != image_log.display_size ||
image_log.data_offset != int2(0);
if (has_display_window) {
this->add_text_field_mono(TIP_("Display/Data Window:"));
this->add_text_field_mono(fmt::format(
"\u2022 {}: {}x{}", TIP_("Data Size"), image_log.data_size.x, image_log.data_size.y));
this->add_text_field_mono(fmt::format("\u2022 {}: {}x{}",
TIP_("Display Size"),
image_log.display_size.x,
image_log.display_size.y));
this->add_text_field_mono(fmt::format("\u2022 {}: ({}, {})",
TIP_("Data Offset"),
image_log.data_offset.x,
image_log.data_offset.y));
}
else {
this->add_text_field_mono(fmt::format(
"{}: {}x{}", TIP_("Resolution"), image_log.data_size.x, image_log.data_size.y));
}
this->add_space();
float2 location;
math::AngleRadian rotation;
float2 scale;
to_loc_rot_scale(image_log.transformation, location, rotation, scale);
const bool is_translated = !math::is_equal(location, float2(0.0f), 10e-6f);
const bool is_rotated = math::abs(rotation.degree()) > 10e-6f;
const bool is_scaled = !math::is_equal(scale, float2(1.0f), 10e-6f);
if (is_translated || is_rotated || is_scaled) {
this->add_text_field_mono(TIP_("Transformation:"));
if (is_translated) {
this->add_text_field_mono(
fmt::format("\u2022 {}: ({}, {})", TIP_("Translation"), location.x, location.y));
}
if (is_rotated) {
this->add_text_field_mono(fmt::format(
"\u2022 {}: {}" BLI_STR_UTF8_DEGREE_SIGN, TIP_("Rotation"), rotation.degree()));
}
if (is_scaled) {
this->add_text_field_mono(
fmt::format("\u2022 {}: ({}, {})", TIP_("Scale"), scale.x, scale.y));
}
}
else {
this->add_text_field_mono(TIP_("Transformation: Identity"));
}
this->add_space();
this->add_text_field_mono(TIP_("Sampling:"));
this->add_text_field_mono(
fmt::format("\u2022 {}: {}", TIP_("Interpolation"), TIP_(image_log.interpolation)));
this->add_text_field_mono(
fmt::format("\u2022 {}: {}", TIP_("Extension X"), TIP_(image_log.extension_x)));
this->add_text_field_mono(
fmt::format("\u2022 {}: {}", TIP_("Extension Y"), TIP_(image_log.extension_y)));
this->add_space();
this->add_text_field_mono(fmt::format("{}: {}", TIP_("Precision"), TIP_(image_log.precision)));
this->add_space();
this->add_text_field_mono(TIP_("Type: Image"));
}
void build_tooltip_value_implicit_default(const NodeDefaultInputType &type)
{
switch (type) {

View file

@ -274,6 +274,33 @@ class ViewerNodeLog {
const bke::GeometrySet *main_geometry() const;
};
/* Compositor image result. */
class ImageInfoLog : public ValueLog {
public:
/* Stores compositor::Domain information. */
const int2 data_size;
const int2 display_size;
const int2 data_offset;
const float3x3 transformation;
/* Stores compositor::RealizationOptions information in a textual representation. */
const StringRefNull interpolation;
const StringRefNull extension_x;
const StringRefNull extension_y;
/* Stores compositor::Result.precision in a textual representation. */
const StringRefNull precision;
ImageInfoLog(int2 data_size,
int2 display_size,
int2 data_offset,
float3x3 transformation,
StringRefNull interpolation,
StringRefNull extension_x,
StringRefNull extension_y,
StringRefNull precision);
};
using Clock = std::chrono::steady_clock;
using TimePoint = Clock::time_point;

View file

@ -275,6 +275,25 @@ NodeWarning::NodeWarning(const Report &report)
this->message = report.message;
}
ImageInfoLog::ImageInfoLog(const int2 data_size,
const int2 display_size,
const int2 data_offset,
const float3x3 transformation,
const StringRefNull interpolation,
const StringRefNull extension_x,
const StringRefNull extension_y,
const StringRefNull precision)
: data_size(data_size),
display_size(display_size),
data_offset(data_offset),
transformation(transformation),
interpolation(interpolation),
extension_x(extension_x),
extension_y(extension_y),
precision(precision)
{
}
/* Avoid generating these in every translation unit. */
NodesEvalLog::NodesEvalLog() = default;
NodesEvalLog::~NodesEvalLog() = default;