Add Chromium-only Blender WebEngine parity work
This commit is contained in:
381
blender-5.2.0/source/blender/compositor/intern/compile_state.cc
Normal file
381
blender-5.2.0/source/blender/compositor/intern/compile_state.cc
Normal file
@@ -0,0 +1,381 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "COM_compile_state.hh"
|
||||
#include "COM_context.hh"
|
||||
#include "COM_domain.hh"
|
||||
#include "COM_implicit_input_operation.hh"
|
||||
#include "COM_input_descriptor.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_pixel_operation.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_scheduler.hh"
|
||||
#include "COM_shader_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
CompileState::CompileState(const Context &context, const Schedule &schedule)
|
||||
: context_(context), schedule_(schedule)
|
||||
{
|
||||
}
|
||||
|
||||
const Schedule &CompileState::get_schedule()
|
||||
{
|
||||
return schedule_;
|
||||
}
|
||||
|
||||
void CompileState::map_node_to_node_operation(const bNode &node, NodeOperation *operations)
|
||||
{
|
||||
node_operations_.add_new(&node, operations);
|
||||
}
|
||||
|
||||
void CompileState::map_node_to_pixel_operation(const bNode &node, PixelOperation *operations)
|
||||
{
|
||||
pixel_operations_.add_new(&node, operations);
|
||||
}
|
||||
|
||||
Result &CompileState::get_result_from_output_socket(const bNodeSocket &output)
|
||||
{
|
||||
/* The output belongs to a node that was compiled into a standard node operation, so return a
|
||||
* reference to the result from that operation using the output identifier. */
|
||||
if (node_operations_.contains(&output.owner_node())) {
|
||||
NodeOperation *operation = node_operations_.lookup(&output.owner_node());
|
||||
return operation->get_result(output.identifier);
|
||||
}
|
||||
|
||||
/* Otherwise, the output belongs to a node that was compiled into a pixel operation, so retrieve
|
||||
* the internal identifier of that output and return a reference to the result from that
|
||||
* operation using the retrieved identifier. */
|
||||
PixelOperation *operation = pixel_operations_.lookup(&output.owner_node());
|
||||
return operation->get_result(operation->get_output_identifier_from_output_socket(output));
|
||||
}
|
||||
|
||||
void CompileState::add_node_to_pixel_compile_unit(const bNode &node)
|
||||
{
|
||||
pixel_compile_unit_.add_new(&node);
|
||||
|
||||
/* If this is the first node in the compile unit, then we should initialize the single value
|
||||
* type, as well as the domain in case the node was not single value. */
|
||||
const bool is_first_node_in_operation = pixel_compile_unit_.size() == 1;
|
||||
if (is_first_node_in_operation) {
|
||||
is_pixel_compile_unit_single_value_ = this->is_pixel_node_single_value(node);
|
||||
|
||||
/* If the node was not a single value, compute and initialize the domain. */
|
||||
if (!is_pixel_compile_unit_single_value_) {
|
||||
pixel_compile_unit_domain_ = this->compute_pixel_node_domain(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PixelCompileUnit &CompileState::get_pixel_compile_unit()
|
||||
{
|
||||
return pixel_compile_unit_;
|
||||
}
|
||||
|
||||
bool CompileState::is_pixel_compile_unit_single_value()
|
||||
{
|
||||
return is_pixel_compile_unit_single_value_;
|
||||
}
|
||||
|
||||
void CompileState::reset_pixel_compile_unit()
|
||||
{
|
||||
pixel_compile_unit_.clear();
|
||||
pixel_compile_unit_domain_.reset();
|
||||
}
|
||||
|
||||
bool CompileState::should_compile_pixel_compile_unit(const bNode &node)
|
||||
{
|
||||
/* If the pixel compile unit is empty, then it can't be compiled yet. */
|
||||
if (pixel_compile_unit_.is_empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If the node is not a pixel node, then it can't be added to the pixel compile unit and the
|
||||
* pixel compile unit is considered complete and should be compiled. */
|
||||
if (!is_pixel_node(node)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* If the compile unit is single value and the given node is not or vice versa, then it can't be
|
||||
* added to the pixel compile unit and the pixel compile unit is considered complete and should
|
||||
* be compiled. */
|
||||
if (is_pixel_compile_unit_single_value_ != this->is_pixel_node_single_value(node)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* For non single value compile units, if the computed domain of the node doesn't matches the
|
||||
* domain of the pixel compile unit, then it can't be added to the pixel compile unit and the
|
||||
* pixel compile unit is considered complete and should be compiled. */
|
||||
if (!is_pixel_compile_unit_single_value_) {
|
||||
if (pixel_compile_unit_domain_.value() != this->compute_pixel_node_domain(node)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Otherwise, the node is compatible and can be added to the compile unit and it shouldn't be
|
||||
* compiled just yet. */
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CompileState::is_pixel_node_single_value(const bNode &node)
|
||||
{
|
||||
/* If any of the outputs are single-only outputs, then the node is operating on single values. */
|
||||
for (const bNodeSocket *output : node.output_sockets()) {
|
||||
if (!is_socket_available(output)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Result::is_single_value_only_type(get_node_socket_result_type(output))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* If any of the inputs are single-only outputs, then the node is operating on single values. */
|
||||
for (const bNodeSocket *input : node.input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Result::is_single_value_only_type(get_node_socket_result_type(input))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* The pixel node is single value when all of its inputs are single values. */
|
||||
for (const bNodeSocket *input : node.input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bNodeSocket *output = get_output_linked_to_input(*input);
|
||||
if (!output) {
|
||||
/* The input does not have an implicit input, so it is a single value. */
|
||||
const InputDescriptor input_descriptor = input_descriptor_from_input_socket(input);
|
||||
if (!input_descriptor.implicit_input.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::optional<Domain> domain = ImplicitInputOperation::get_domain(
|
||||
context_, input_descriptor.implicit_input.value());
|
||||
if (!domain.has_value()) {
|
||||
/* The input has an implicit input, but it is a single value. */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Otherwise, it has an non-single-value implicit input. */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If the output belongs to a node that is part of the pixel compile unit and that compile unit
|
||||
* is not single value, then the node is not single value. */
|
||||
if (pixel_compile_unit_.contains(&output->owner_node())) {
|
||||
if (is_pixel_compile_unit_single_value_) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const Result &result = get_result_from_output_socket(*output);
|
||||
if (!result.is_single_value()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Domain CompileState::compute_pixel_node_domain(const bNode &node)
|
||||
{
|
||||
/* Default to an identity domain in case no domain input was found, most likely because all
|
||||
* inputs are single values. */
|
||||
Domain node_domain = Domain::identity();
|
||||
int current_domain_priority = std::numeric_limits<int>::max();
|
||||
|
||||
/* Go over the inputs and find the domain of the non single value input with the highest domain
|
||||
* priority. */
|
||||
for (const bNodeSocket *input : node.input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const InputDescriptor input_descriptor = input_descriptor_from_input_socket(input);
|
||||
|
||||
const bNodeSocket *output = get_output_linked_to_input(*input);
|
||||
if (!output) {
|
||||
/* The input does not have an implicit input, so it is a single that can't be a domain input
|
||||
* and we skip it. */
|
||||
if (!input_descriptor.implicit_input.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::optional<Domain> domain = ImplicitInputOperation::get_domain(
|
||||
context_, input_descriptor.implicit_input.value());
|
||||
if (!domain.has_value()) {
|
||||
/* The input has an implicit input, but it is a single value that can't be a domain input
|
||||
* and we skip it. */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Otherwise, the input has the domain of the implicit input, which is the domain of the
|
||||
* compositing region. Notice that the lower the domain priority value is, the higher the
|
||||
* priority is, hence the less than comparison. */
|
||||
if (input_descriptor.domain_priority < current_domain_priority) {
|
||||
node_domain = domain.value();
|
||||
current_domain_priority = input_descriptor.domain_priority;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If the output belongs to a node that is part of the pixel compile unit, then the domain of
|
||||
* the input is the domain of the compile unit itself. */
|
||||
if (pixel_compile_unit_.contains(&output->owner_node())) {
|
||||
/* Notice that the lower the domain priority value is, the higher the priority is, hence the
|
||||
* less than comparison. */
|
||||
if (input_descriptor.domain_priority < current_domain_priority) {
|
||||
node_domain = pixel_compile_unit_domain_.value();
|
||||
current_domain_priority = input_descriptor.domain_priority;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const Result &result = get_result_from_output_socket(*output);
|
||||
|
||||
/* A single value input can't be a domain input. */
|
||||
if (result.is_single_value() || input_descriptor.expects_single_value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* An input that skips operation domain realization can't be a domain input. */
|
||||
if (input_descriptor.realization_mode != InputRealizationMode::OperationDomain) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Notice that the lower the domain priority value is, the higher the priority is, hence the
|
||||
* less than comparison. */
|
||||
if (input_descriptor.domain_priority < current_domain_priority) {
|
||||
node_domain = result.domain();
|
||||
current_domain_priority = input_descriptor.domain_priority;
|
||||
}
|
||||
}
|
||||
|
||||
return node_domain;
|
||||
}
|
||||
|
||||
bool CompileState::pixel_compile_unit_has_too_many_outputs(const bool are_node_previews_needed)
|
||||
{
|
||||
/* Only GPU and non-single units have output count limitations. */
|
||||
if (!context_.use_gpu() || is_pixel_compile_unit_single_value_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int outputs_count = 0;
|
||||
for (const bNode *node : pixel_compile_unit_) {
|
||||
const bNodeSocket *preview_output = are_node_previews_needed ?
|
||||
find_preview_output_socket(*node) :
|
||||
nullptr;
|
||||
|
||||
for (const bNodeSocket *output : node->output_sockets()) {
|
||||
if (!is_socket_available(output)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If the output is used as the node preview, then an operation output will exist for it. */
|
||||
const bool is_preview_output = output == preview_output;
|
||||
|
||||
/* If any of the nodes linked to the output are not part of the pixel compile unit but are
|
||||
* part of the execution schedule, then an operation output will exist for it. */
|
||||
const bool is_operation_output = is_output_linked_to_input_conditioned(
|
||||
*output, [&](const bNodeSocket &input) {
|
||||
return schedule_.nodes.contains(&input.owner_node()) &&
|
||||
!schedule_.unneeded_inputs.contains(&input) &&
|
||||
!pixel_compile_unit_.contains(&input.owner_node());
|
||||
});
|
||||
|
||||
if (is_operation_output || is_preview_output) {
|
||||
outputs_count += 1;
|
||||
}
|
||||
|
||||
if (outputs_count > ShaderOperation::maximum_outputs_count) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CompileState::pixel_compile_unit_has_too_many_inputs()
|
||||
{
|
||||
/* Only GPU and non-single units have input count limitations. */
|
||||
if (!context_.use_gpu() || is_pixel_compile_unit_single_value_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<ImplicitInputType> referenced_implicit_inputs;
|
||||
Set<const bNodeSocket *> referenced_output_sockets;
|
||||
int inputs_count = 0;
|
||||
for (const bNode *node : pixel_compile_unit_) {
|
||||
for (const bNodeSocket *input : node->input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bNodeSocket *output = get_output_linked_to_input(*input);
|
||||
if (!output) {
|
||||
const InputDescriptor input_descriptor = input_descriptor_from_input_socket(input);
|
||||
if (!input_descriptor.implicit_input.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* All implicit inputs of the same type share the same input, and this one was counted
|
||||
* before, so no need to count it again. */
|
||||
if (referenced_implicit_inputs.contains(input_descriptor.implicit_input.value())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
inputs_count++;
|
||||
if (inputs_count > ShaderOperation::maximum_inputs_count) {
|
||||
return true;
|
||||
}
|
||||
|
||||
referenced_implicit_inputs.add_new(input_descriptor.implicit_input.value());
|
||||
continue;
|
||||
}
|
||||
|
||||
/* This output is part of the pixel compile unit, so no input is declared for it. */
|
||||
if (pixel_compile_unit_.contains(&output->owner_node())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* All inputs linked to the same output share the same input, and this one was counted
|
||||
* before, so no need to count it again. */
|
||||
if (referenced_output_sockets.contains(output)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
inputs_count++;
|
||||
if (inputs_count > ShaderOperation::maximum_inputs_count) {
|
||||
return true;
|
||||
}
|
||||
|
||||
referenced_output_sockets.add_new(output);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
129
blender-5.2.0/source/blender/compositor/intern/context.cc
Normal file
129
blender-5.2.0/source/blender/compositor/intern/context.cc
Normal file
@@ -0,0 +1,129 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "GPU_shader.hh"
|
||||
|
||||
#include "COM_context.hh"
|
||||
#include "COM_render_context.hh"
|
||||
#include "COM_static_cache_manager.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
Context::Context(StaticCacheManager &cache_manager) : cache_manager_(cache_manager) {};
|
||||
|
||||
Result Context::get_pass(const Scene * /*scene*/, int /*view_layer*/, const char * /*name*/)
|
||||
{
|
||||
compositor::Result invalid_pass = this->create_result(compositor::ResultType::Color);
|
||||
invalid_pass.allocate_invalid();
|
||||
return invalid_pass;
|
||||
}
|
||||
|
||||
const RenderData &Context::get_render_data() const
|
||||
{
|
||||
return this->get_scene().r;
|
||||
}
|
||||
|
||||
StringRef Context::get_view_name() const
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
ResultPrecision Context::get_precision() const
|
||||
{
|
||||
return ResultPrecision::Full;
|
||||
}
|
||||
|
||||
void Context::set_info_message(StringRef /*message*/) const {}
|
||||
|
||||
bool Context::treat_viewer_as_group_output() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void Context::populate_meta_data_for_pass(const Scene * /*scene*/,
|
||||
int /*view_layer_id*/,
|
||||
const char * /*pass_name*/,
|
||||
MetaData & /*meta_data*/) const
|
||||
{
|
||||
}
|
||||
|
||||
RenderContext *Context::render_context() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nodes::eval_log::NodesEvalLog *Context::nodes_evaluation_log() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Context::evaluate_operation_post() const {}
|
||||
|
||||
bool Context::is_canceled() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float Context::get_render_percentage() const
|
||||
{
|
||||
return get_render_data().size / 100.0f;
|
||||
}
|
||||
|
||||
int Context::get_frame_number() const
|
||||
{
|
||||
return get_render_data().cfra;
|
||||
}
|
||||
|
||||
float Context::get_time() const
|
||||
{
|
||||
const float frame_number = float(get_frame_number());
|
||||
const float frame_rate = float(get_render_data().frs_sec) /
|
||||
float(get_render_data().frs_sec_base);
|
||||
return frame_number / frame_rate;
|
||||
}
|
||||
|
||||
eCompositorDenoiseQaulity Context::get_denoise_quality() const
|
||||
{
|
||||
if (this->render_context()) {
|
||||
return static_cast<eCompositorDenoiseQaulity>(
|
||||
this->get_render_data().compositor_denoise_final_quality);
|
||||
}
|
||||
|
||||
return static_cast<eCompositorDenoiseQaulity>(
|
||||
this->get_render_data().compositor_denoise_preview_quality);
|
||||
}
|
||||
|
||||
gpu::Shader *Context::get_shader(const char *info_name, ResultPrecision precision)
|
||||
{
|
||||
return cache_manager().cached_shaders.get(info_name, precision);
|
||||
}
|
||||
|
||||
gpu::Shader *Context::get_shader(const char *info_name)
|
||||
{
|
||||
return get_shader(info_name, get_precision());
|
||||
}
|
||||
|
||||
Result Context::create_result(ResultType type, ResultPrecision precision)
|
||||
{
|
||||
return Result(*this, type, precision);
|
||||
}
|
||||
|
||||
Result Context::create_result(ResultType type)
|
||||
{
|
||||
return create_result(type, get_precision());
|
||||
}
|
||||
|
||||
StaticCacheManager &Context::cache_manager()
|
||||
{
|
||||
return cache_manager_;
|
||||
}
|
||||
|
||||
const Strip *Context::get_strip() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
@@ -0,0 +1,112 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "BLI_generic_span.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "GPU_shader.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#include "BKE_type_conversions.hh"
|
||||
|
||||
#include "COM_context.hh"
|
||||
#include "COM_conversion_operation.hh"
|
||||
#include "COM_input_descriptor.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
ConversionOperation::ConversionOperation(Context &context,
|
||||
const ResultType input_type,
|
||||
const ResultType expected_type)
|
||||
: SimpleOperation(context)
|
||||
{
|
||||
this->declare_input_descriptor(InputDescriptor{input_type});
|
||||
this->populate_result(expected_type);
|
||||
}
|
||||
|
||||
void ConversionOperation::execute()
|
||||
{
|
||||
Result &result = this->get_result();
|
||||
const Result &input = this->get_input();
|
||||
|
||||
const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions();
|
||||
if (!conversions.is_convertible(input.get_cpp_type(), result.get_cpp_type())) {
|
||||
this->allocate_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.is_single_value()) {
|
||||
result.allocate_single_value();
|
||||
this->execute_single(input, result);
|
||||
return;
|
||||
}
|
||||
|
||||
result.allocate_texture(input.domain());
|
||||
if (this->context().use_gpu()) {
|
||||
const std::string shader_name = fmt::format("compositor_convert_{}_to_{}",
|
||||
Result::type_name(this->get_input().type()),
|
||||
Result::type_name(this->get_result().type()));
|
||||
gpu::Shader *shader = this->context().get_shader(shader_name.c_str());
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
if (this->get_input().type() == ResultType::Color &&
|
||||
ELEM(this->get_result().type(), ResultType::Float, ResultType::Int, ResultType::Bool))
|
||||
{
|
||||
float luminance_coefficients[3];
|
||||
IMB_colormanagement_get_luminance_coefficients(luminance_coefficients);
|
||||
GPU_shader_uniform_3fv(shader, "luminance_coefficients_u", luminance_coefficients);
|
||||
}
|
||||
|
||||
input.bind_as_texture(shader, "input_tx");
|
||||
result.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, input.domain().data_size);
|
||||
|
||||
input.unbind_as_texture();
|
||||
result.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
else {
|
||||
this->execute_cpu(input, result);
|
||||
}
|
||||
}
|
||||
|
||||
SimpleOperation *ConversionOperation::construct_if_needed(Context &context,
|
||||
const Result &input_result,
|
||||
const InputDescriptor &input_descriptor)
|
||||
{
|
||||
if (input_descriptor.skip_type_conversion) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ResultType result_type = input_result.type();
|
||||
const ResultType expected_type = input_descriptor.type;
|
||||
if (result_type != expected_type) {
|
||||
return new ConversionOperation(context, result_type, expected_type);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ConversionOperation::execute_single(const Result &input, Result &output)
|
||||
{
|
||||
const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions();
|
||||
conversions.convert_to_initialized_n(
|
||||
GSpan(input.single_value().type(), input.single_value().get(), 1),
|
||||
GMutableSpan(output.single_value().type(), output.single_value().get(), 1));
|
||||
output.update_single_value_data();
|
||||
}
|
||||
|
||||
void ConversionOperation::execute_cpu(const Result &input, Result &output)
|
||||
{
|
||||
const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions();
|
||||
conversions.convert_to_initialized_n(input.cpu_data(), output.cpu_data_for_write());
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
161
blender-5.2.0/source/blender/compositor/intern/domain.cc
Normal file
161
blender-5.2.0/source/blender/compositor/intern/domain.cc
Normal file
@@ -0,0 +1,161 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_bounds.hh"
|
||||
#include "BLI_bounds_types.hh"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "GPU_texture.hh"
|
||||
|
||||
#include "COM_domain.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
Domain::Domain(const int2 &size)
|
||||
: data_size(size),
|
||||
display_size(size),
|
||||
data_offset(int2(0)),
|
||||
transformation(float3x3::identity())
|
||||
{
|
||||
}
|
||||
|
||||
Domain::Domain(const int2 &size, const float3x3 &transformation)
|
||||
: data_size(size), display_size(size), data_offset(int2(0)), transformation(transformation)
|
||||
{
|
||||
}
|
||||
|
||||
void Domain::transform(const float3x3 &input_transformation)
|
||||
{
|
||||
transformation = input_transformation * transformation;
|
||||
}
|
||||
|
||||
Domain Domain::transposed() const
|
||||
{
|
||||
Domain domain = *this;
|
||||
domain.data_size = int2(this->data_size.y, this->data_size.x);
|
||||
domain.display_size = int2(this->display_size.y, this->display_size.x);
|
||||
domain.data_offset = int2(this->data_offset.y, this->data_offset.x);
|
||||
return domain;
|
||||
}
|
||||
|
||||
Domain Domain::identity()
|
||||
{
|
||||
return Domain(int2(1), float3x3::identity());
|
||||
}
|
||||
|
||||
bool Domain::is_equal(const Domain &a, const Domain &b, const float epsilon)
|
||||
{
|
||||
return a.data_size == b.data_size && a.display_size == b.display_size &&
|
||||
a.data_offset == b.data_offset &&
|
||||
math::is_equal(a.transformation, b.transformation, epsilon);
|
||||
}
|
||||
|
||||
Domain Domain::realize_transformation(const bool realize_translation) const
|
||||
{
|
||||
/* If the domain is only infinitesimally rotated or scaled, only realize the translation if
|
||||
* needed, otherwise, return as is. */
|
||||
const float3x3 translation = math::from_location<float3x3>(this->transformation.location());
|
||||
if (math::is_equal(float2x2(this->transformation), float2x2::identity(), 10e-6f)) {
|
||||
Domain realized_domain = *this;
|
||||
realized_domain.transformation = realize_translation ? float3x3::identity() : translation;
|
||||
return realized_domain;
|
||||
}
|
||||
|
||||
/* Eliminate the translation component of the transformation. Translation is ignored since it has
|
||||
* no effect on the size of the domain and will be restored later if needed. */
|
||||
const float3x3 transformation = float3x3(float2x2(this->transformation));
|
||||
|
||||
/* Translate the input such that it is centered in the virtual compositing space. */
|
||||
const float2 center_translation = -float2(this->display_size) / 2.0f;
|
||||
const float3x3 centered_transformation = math::translate(transformation, center_translation);
|
||||
|
||||
/* Compute display window after transformation. */
|
||||
const Bounds<float2> display_window = {float2(0.0f), float2(this->display_size)};
|
||||
const Bounds<float2> new_display_window = bounds::transform_bounds(centered_transformation,
|
||||
display_window);
|
||||
const Bounds<int2> new_integer_display_window = {int2(math::floor(new_display_window.min)),
|
||||
int2(math::ceil(new_display_window.max))};
|
||||
|
||||
/* Compute data window after transformation. */
|
||||
const Bounds<float2> data_window = {float2(this->data_offset),
|
||||
float2(this->data_offset + this->data_size)};
|
||||
const Bounds<float2> new_data_window = bounds::transform_bounds(centered_transformation,
|
||||
data_window);
|
||||
const Bounds<int2> new_integer_data_window = {int2(math::floor(new_data_window.min)),
|
||||
int2(math::ceil(new_data_window.max))};
|
||||
|
||||
Domain realized_domain = *this;
|
||||
realized_domain.display_size = math::max(int2(1), new_integer_display_window.size());
|
||||
realized_domain.data_size = math::max(int2(1), new_integer_data_window.size());
|
||||
realized_domain.data_offset = new_integer_data_window.min - new_integer_display_window.min;
|
||||
realized_domain.transformation = realize_translation ? float3x3::identity() : translation;
|
||||
return realized_domain;
|
||||
}
|
||||
|
||||
bool operator==(const Domain &a, const Domain &b)
|
||||
{
|
||||
return a.data_size == b.data_size && a.display_size == b.display_size &&
|
||||
a.data_offset == b.data_offset && a.transformation == b.transformation;
|
||||
}
|
||||
|
||||
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) {
|
||||
case compositor::Extension::Clip:
|
||||
return GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER;
|
||||
|
||||
case compositor::Extension::Extend:
|
||||
return GPU_SAMPLER_EXTEND_MODE_EXTEND;
|
||||
|
||||
case compositor::Extension::Repeat:
|
||||
return GPU_SAMPLER_EXTEND_MODE_REPEAT;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER;
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
@@ -0,0 +1,58 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "COM_context.hh"
|
||||
#include "COM_group_input_node_operation.hh"
|
||||
#include "COM_node_group_operation.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
/* A node operation representing a group input node that for each of its outputs gets the input
|
||||
* from the node group operation it represents and shares its data with its own output with the
|
||||
* same identifier. */
|
||||
class GroupInputNodeOperation : public NodeOperation {
|
||||
private:
|
||||
/* The node group operation that this group input node belongs to. */
|
||||
NodeGroupOperation &node_group_operation_;
|
||||
|
||||
public:
|
||||
GroupInputNodeOperation(Context &context,
|
||||
const bNode &node,
|
||||
NodeGroupOperation &node_group_operation)
|
||||
: NodeOperation(context, node), node_group_operation_(node_group_operation)
|
||||
{
|
||||
}
|
||||
|
||||
void execute() override
|
||||
{
|
||||
for (const bNodeSocket *output_socket : this->node().output_sockets()) {
|
||||
if (!is_socket_available(output_socket)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Result &output_result = this->get_result(output_socket->identifier);
|
||||
if (output_result.should_compute()) {
|
||||
const Result &node_group_operation_input = node_group_operation_.get_input(
|
||||
output_socket->identifier);
|
||||
output_result.share_data(node_group_operation_input);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NodeOperation *get_group_input_node_operation(Context &context,
|
||||
const bNode &node,
|
||||
NodeGroupOperation &node_group_operation)
|
||||
{
|
||||
return new GroupInputNodeOperation(context, node, node_group_operation);
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
@@ -0,0 +1,142 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "COM_group_node_operation.hh"
|
||||
#include "COM_node_group_operation.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
/* A node operation representing a group node. This is a thin wrapper around a NodeGroupOperation
|
||||
* mapping its own inputs to the inputs of the node group operation and sharing its results with
|
||||
* the results of the node group operation. */
|
||||
class GroupNodeOperation : public NodeOperation {
|
||||
private:
|
||||
/* The node group outputs needed by the caller. */
|
||||
const NodeGroupOutputTypes needed_outputs_;
|
||||
/* The node instance key of the active group node. */
|
||||
const bNodeInstanceKey active_node_group_instance_key_ = bke::NODE_INSTANCE_KEY_BASE;
|
||||
|
||||
public:
|
||||
GroupNodeOperation(Context &context,
|
||||
const bNode &node,
|
||||
const NodeGroupOutputTypes needed_outputs,
|
||||
const bNodeInstanceKey active_node_group_instance_key)
|
||||
: NodeOperation(context, node),
|
||||
needed_outputs_(needed_outputs),
|
||||
active_node_group_instance_key_(active_node_group_instance_key)
|
||||
{
|
||||
for (const bNodeSocket *input : node.input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
InputDescriptor &descriptor = this->get_input_descriptor(input->identifier);
|
||||
/* The structure type of the inputs of Group nodes are inferred, so we need to make sure this
|
||||
* is not wrongly expecting single values. */
|
||||
descriptor.expects_single_value = false;
|
||||
/* Groups nodes should not force realization since it is defined by the user, and there is
|
||||
* currently no way for the user to define that through the UI. */
|
||||
descriptor.realization_mode = InputRealizationMode::None;
|
||||
}
|
||||
}
|
||||
|
||||
void execute() override
|
||||
{
|
||||
const bNodeTree *node_group = this->get_node_group();
|
||||
if (!node_group) {
|
||||
this->allocate_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
|
||||
const bke::GroupNodeComputeContext compute_context(
|
||||
&this->get_compute_context(), this->node().identifier, &this->node().owner_tree());
|
||||
NodeGroupOperation operation(this->context(),
|
||||
*node_group,
|
||||
needed_outputs_,
|
||||
active_node_group_instance_key_,
|
||||
this->get_instance_key(),
|
||||
compute_context);
|
||||
|
||||
this->set_reference_counts(operation);
|
||||
Vector<std::unique_ptr<Result>> temporary_inputs = this->map_inputs(operation);
|
||||
operation.evaluate();
|
||||
this->write_outputs(operation);
|
||||
}
|
||||
|
||||
/* Sets the reference counts of the node group operation according to the needed status of the
|
||||
* outputs of the group node. */
|
||||
void set_reference_counts(Operation &operation)
|
||||
{
|
||||
const bNodeTree *node_group = this->get_node_group();
|
||||
node_group->ensure_interface_cache();
|
||||
for (const bNodeTreeInterfaceSocket *output_socket : node_group->interface_outputs()) {
|
||||
Result &node_group_result = operation.get_result(output_socket->identifier);
|
||||
Result &group_node_result = this->get_result(output_socket->identifier);
|
||||
node_group_result.set_reference_count(group_node_result.should_compute() ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Maps the input results of the node group operation to this group node's inputs through
|
||||
* temporary results that share the data of the this group's inputs. */
|
||||
Vector<std::unique_ptr<Result>> map_inputs(Operation &operation)
|
||||
{
|
||||
const bNodeTree *node_group = this->get_node_group();
|
||||
Vector<std::unique_ptr<Result>> temporary_inputs;
|
||||
node_group->ensure_interface_cache();
|
||||
for (const bNodeTreeInterfaceSocket *input_socket : node_group->interface_inputs()) {
|
||||
const Result &input_result = this->get_input(input_socket->identifier);
|
||||
std::unique_ptr<Result> temporary_input = std::make_unique<Result>(
|
||||
this->context().create_result(input_result.type(), input_result.precision()));
|
||||
temporary_input->share_data(input_result);
|
||||
temporary_inputs.append(std::move(temporary_input));
|
||||
operation.map_input_to_result(input_socket->identifier, temporary_inputs.last().get());
|
||||
}
|
||||
return temporary_inputs;
|
||||
}
|
||||
|
||||
/* Writes the output results of the node group operation to this group node operation by sharing
|
||||
* its data and freeing the results. */
|
||||
void write_outputs(Operation &operation)
|
||||
{
|
||||
const bNodeTree *node_group = this->get_node_group();
|
||||
node_group->ensure_interface_cache();
|
||||
for (const bNodeTreeInterfaceSocket *output_socket : node_group->interface_outputs()) {
|
||||
Result &node_group_result = operation.get_result(output_socket->identifier);
|
||||
Result &group_node_result = this->get_result(output_socket->identifier);
|
||||
if (group_node_result.should_compute()) {
|
||||
group_node_result.share_data(node_group_result);
|
||||
node_group_result.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bNodeTree *get_node_group()
|
||||
{
|
||||
BLI_assert(this->node().is_group());
|
||||
return reinterpret_cast<const bNodeTree *>(this->node().id);
|
||||
}
|
||||
};
|
||||
|
||||
NodeOperation *get_group_node_operation(Context &context,
|
||||
const bNode &node,
|
||||
const NodeGroupOutputTypes &needed_outputs,
|
||||
const bNodeInstanceKey active_node_group_instance_key)
|
||||
{
|
||||
return new GroupNodeOperation(context, node, needed_outputs, active_node_group_instance_key);
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
@@ -0,0 +1,70 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "COM_context.hh"
|
||||
#include "COM_group_output_node_operation.hh"
|
||||
#include "COM_node_group_operation.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
/* A node operation representing a group output node that for each of its inputs gets the input
|
||||
* and shares its data with the result of the node group operation it represents with the same
|
||||
* identifier. */
|
||||
class GroupOutputNodeOperation : public NodeOperation {
|
||||
private:
|
||||
NodeGroupOperation &node_group_operation_;
|
||||
|
||||
public:
|
||||
GroupOutputNodeOperation(Context &context,
|
||||
const bNode &node,
|
||||
NodeGroupOperation &node_group_operation)
|
||||
: NodeOperation(context, node), node_group_operation_(node_group_operation)
|
||||
{
|
||||
for (const bNodeSocket *input : node.input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
InputDescriptor &descriptor = this->get_input_descriptor(input->identifier);
|
||||
/* The structure type of the inputs of Group Output nodes are inferred, so we need to
|
||||
* make sure this is not wrongly expecting single values. */
|
||||
descriptor.expects_single_value = false;
|
||||
/* Groups Output nodes should not force realization since it is defined by the user, and
|
||||
* there is currently no way for the user to define that through the UI. */
|
||||
descriptor.realization_mode = InputRealizationMode::None;
|
||||
}
|
||||
}
|
||||
|
||||
void execute() override
|
||||
{
|
||||
for (const bNodeSocket *input_socket : this->node().input_sockets()) {
|
||||
if (!is_socket_available(input_socket)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Result &node_group_operation_result = node_group_operation_.get_result(
|
||||
input_socket->identifier);
|
||||
if (node_group_operation_result.should_compute()) {
|
||||
const Result &input_result = this->get_input(input_socket->identifier);
|
||||
node_group_operation_result.share_data(input_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NodeOperation *get_group_output_node_operation(Context &context,
|
||||
const bNode &node,
|
||||
NodeGroupOperation &node_group_operation)
|
||||
{
|
||||
return new GroupOutputNodeOperation(context, node, node_group_operation);
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
@@ -0,0 +1,76 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "BLI_assert.h"
|
||||
|
||||
#include "COM_domain.hh"
|
||||
#include "COM_implicit_input_operation.hh"
|
||||
#include "COM_input_descriptor.hh"
|
||||
#include "COM_operation.hh"
|
||||
#include "COM_result.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
const StringRef ImplicitInputOperation::output_identifier_ = StringRef("Output");
|
||||
|
||||
static ResultType get_implicit_input_result_type(const ImplicitInputType implicit_input)
|
||||
{
|
||||
switch (implicit_input) {
|
||||
case ImplicitInputType::UniformImageCoordinates:
|
||||
return ResultType::Float2;
|
||||
case ImplicitInputType::SceneFrame:
|
||||
return ResultType::Int;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return ResultType::Float2;
|
||||
}
|
||||
|
||||
ImplicitInputOperation::ImplicitInputOperation(Context &context,
|
||||
const ImplicitInputType implicit_input)
|
||||
: Operation(context), implicit_input_(implicit_input)
|
||||
{
|
||||
this->populate_result(output_identifier_, get_implicit_input_result_type(implicit_input));
|
||||
}
|
||||
|
||||
void ImplicitInputOperation::execute()
|
||||
{
|
||||
Result &result = this->get_result();
|
||||
|
||||
switch (implicit_input_) {
|
||||
case ImplicitInputType::UniformImageCoordinates: {
|
||||
const int2 size = this->context().get_compositing_domain().data_size;
|
||||
result.share_data(this->context().cache_manager().image_coordinates.get(
|
||||
this->context(), size, CoordinatesType::Uniform));
|
||||
break;
|
||||
}
|
||||
case ImplicitInputType::SceneFrame:
|
||||
result.allocate_single_value();
|
||||
result.set_single_value(this->context().get_frame_number());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Result &ImplicitInputOperation::get_result()
|
||||
{
|
||||
return Operation::get_result(output_identifier_);
|
||||
}
|
||||
|
||||
std::optional<Domain> ImplicitInputOperation::get_domain(const Context &context,
|
||||
const ImplicitInputType implicit_input)
|
||||
{
|
||||
switch (implicit_input) {
|
||||
case ImplicitInputType::UniformImageCoordinates:
|
||||
return context.get_compositing_domain();
|
||||
case ImplicitInputType::SceneFrame:
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
15
blender-5.2.0/source/blender/compositor/intern/meta_data.cc
Normal file
15
blender-5.2.0/source/blender/compositor/intern/meta_data.cc
Normal file
@@ -0,0 +1,15 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "COM_meta_data.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
bool MetaData::is_cryptomatte_layer() const
|
||||
{
|
||||
return !this->cryptomatte.manifest.empty() || !this->cryptomatte.hash.empty() ||
|
||||
!this->cryptomatte.conversion.empty();
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
@@ -0,0 +1,534 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_cpp_type.hh"
|
||||
#include "BLI_generic_span.hh"
|
||||
#include "BLI_index_mask.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_euler.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "FN_multi_function.hh"
|
||||
#include "FN_multi_function_builder.hh"
|
||||
#include "FN_multi_function_context.hh"
|
||||
#include "FN_multi_function_data_type.hh"
|
||||
#include "FN_multi_function_procedure.hh"
|
||||
#include "FN_multi_function_procedure_builder.hh"
|
||||
#include "FN_multi_function_procedure_executor.hh"
|
||||
#include "FN_multi_function_procedure_optimization.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
#include "BKE_type_conversions.hh"
|
||||
|
||||
#include "NOD_multi_function.hh"
|
||||
|
||||
#include "COM_context.hh"
|
||||
#include "COM_domain.hh"
|
||||
#include "COM_input_descriptor.hh"
|
||||
#include "COM_multi_function_procedure_operation.hh"
|
||||
#include "COM_pixel_operation.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_scheduler.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
MultiFunctionProcedureOperation::MultiFunctionProcedureOperation(
|
||||
Context &context,
|
||||
PixelCompileUnit &compile_unit,
|
||||
const Schedule &schedule,
|
||||
const bool is_single_value,
|
||||
const ComputeContext &compute_context)
|
||||
: PixelOperation(context, compile_unit, schedule, compute_context, is_single_value),
|
||||
procedure_builder_(procedure_)
|
||||
{
|
||||
this->build_procedure();
|
||||
procedure_executor_ = std::make_unique<mf::ProcedureExecutor>(procedure_);
|
||||
}
|
||||
|
||||
void MultiFunctionProcedureOperation::execute()
|
||||
{
|
||||
const Domain domain = is_single_value_ ? Domain(int2(1)) : this->compute_domain();
|
||||
const int64_t size = int64_t(domain.data_size.x) * domain.data_size.y;
|
||||
const IndexMask mask = IndexMask(size);
|
||||
mf::ParamsBuilder parameter_builder{*procedure_executor_, &mask};
|
||||
|
||||
/* For each of the parameters, either add an input or an output depending on its interface type,
|
||||
* allocating the outputs when needed. */
|
||||
for (int i = 0; i < procedure_.params().size(); i++) {
|
||||
if (procedure_.params()[i].type == mf::ParamType::InterfaceType::Input) {
|
||||
const Result &input = get_input(parameter_identifiers_[i]);
|
||||
if (input.is_single_value()) {
|
||||
parameter_builder.add_readonly_single_input(input.single_value());
|
||||
}
|
||||
else {
|
||||
if (is_single_value_) {
|
||||
/* The operation is operating on single values but an image is provided, so add a default
|
||||
* single value as a fallback. */
|
||||
parameter_builder.add_readonly_single_input(
|
||||
GPointer(input.get_cpp_type(), input.get_cpp_type().default_value()));
|
||||
}
|
||||
else {
|
||||
parameter_builder.add_readonly_single_input(input.cpu_data());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Result &output = get_result(parameter_identifiers_[i]);
|
||||
if (is_single_value_) {
|
||||
output.allocate_single_value();
|
||||
parameter_builder.add_uninitialized_single_output(
|
||||
GMutableSpan(output.get_cpp_type(), output.single_value().get(), 1));
|
||||
}
|
||||
else {
|
||||
output.allocate_texture(domain);
|
||||
parameter_builder.add_uninitialized_single_output(output.cpu_data_for_write());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mf::ContextBuilder context_builder;
|
||||
procedure_executor_->call_auto(mask, parameter_builder, context_builder);
|
||||
|
||||
/* In case of single value execution, update single value data. */
|
||||
if (is_single_value_) {
|
||||
for (int i = 0; i < procedure_.params().size(); i++) {
|
||||
if (procedure_.params()[i].type == mf::ParamType::InterfaceType::Output) {
|
||||
Result &output = get_result(parameter_identifiers_[i]);
|
||||
output.update_single_value_data();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiFunctionProcedureOperation::build_procedure()
|
||||
{
|
||||
for (const bNode *node : compile_unit_) {
|
||||
/* Get the multi-function of the node. */
|
||||
auto &multi_function_builder = *node_multi_functions_.lookup_or_add_cb(node, [&]() {
|
||||
return std::make_unique<nodes::NodeMultiFunctionBuilder>(*node, node->owner_tree());
|
||||
});
|
||||
node->typeinfo->build_multi_function(multi_function_builder);
|
||||
const mf::MultiFunction &multi_function = multi_function_builder.function();
|
||||
|
||||
/* Get the variables of the inputs of the node, creating inputs to the operation/procedure if
|
||||
* needed. */
|
||||
Vector<mf::Variable *> input_variables = this->get_input_variables(*node, multi_function);
|
||||
|
||||
/* Call the node multi-function, getting the variables for its outputs. */
|
||||
Vector<mf::Variable *> output_variables = procedure_builder_.add_call(multi_function,
|
||||
input_variables);
|
||||
|
||||
/* Assign the output variables to the node's respective outputs, creating outputs for the
|
||||
* operation/procedure if needed. */
|
||||
this->assign_output_variables(*node, output_variables);
|
||||
}
|
||||
|
||||
/* Add destructor calls for the variables. */
|
||||
for (const auto &item : output_to_variable_map_.items()) {
|
||||
/* Variables that are used by the outputs should not be destructed. */
|
||||
if (!output_variables_.contains(item.value)) {
|
||||
procedure_builder_.add_destruct(*item.value);
|
||||
}
|
||||
}
|
||||
for (mf::Variable *variable : implicit_variables_) {
|
||||
/* Variables that are used by the outputs should not be destructed. */
|
||||
if (!output_variables_.contains(variable)) {
|
||||
procedure_builder_.add_destruct(*variable);
|
||||
}
|
||||
}
|
||||
for (mf::Variable *variable : implicit_input_to_variable_map_.values()) {
|
||||
procedure_builder_.add_destruct(*variable);
|
||||
}
|
||||
|
||||
mf::ReturnInstruction &return_instruction = procedure_builder_.add_return();
|
||||
procedure_.prepare_for_execution();
|
||||
mf::procedure_optimization::move_destructs_up(procedure_, return_instruction);
|
||||
BLI_assert(procedure_.validate());
|
||||
}
|
||||
|
||||
Vector<mf::Variable *> MultiFunctionProcedureOperation::get_input_variables(
|
||||
const bNode &node, const mf::MultiFunction &multi_function)
|
||||
{
|
||||
int available_inputs_index = 0;
|
||||
Vector<mf::Variable *> input_variables;
|
||||
for (const bNodeSocket *input : node.input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mf::ParamType parameter_type = multi_function.param_type(available_inputs_index);
|
||||
available_inputs_index++;
|
||||
|
||||
if (schedule_.unneeded_inputs.contains(input)) {
|
||||
input_variables.append(this->get_default_value_variable(parameter_type.data_type()));
|
||||
continue;
|
||||
}
|
||||
|
||||
const bNodeSocket *output = get_output_linked_to_input(*input);
|
||||
if (!output) {
|
||||
const InputDescriptor input_descriptor = input_descriptor_from_input_socket(input);
|
||||
if (!input_descriptor.implicit_input.has_value()) {
|
||||
/* No implicit input, so get a constant variable that holds the socket value. */
|
||||
input_variables.append(this->get_constant_input_variable(*input));
|
||||
}
|
||||
else {
|
||||
input_variables.append(this->get_implicit_input_variable(*input));
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* If the source node is part of the multi-function procedure operation, then the output has
|
||||
* an existing variable for it. */
|
||||
if (compile_unit_.contains(&output->owner_node())) {
|
||||
input_variables.append(output_to_variable_map_.lookup(output));
|
||||
}
|
||||
else {
|
||||
/* Otherwise, the source node is not part of the multi-function procedure operation, and a
|
||||
* variable that represents an input to the multi-function procedure operation is used. */
|
||||
input_variables.append(this->get_multi_function_input_variable(*input, *output));
|
||||
}
|
||||
}
|
||||
|
||||
/* Implicitly convert the variable type to the expected parameter type if needed. */
|
||||
input_variables.last() = this->convert_variable(input_variables.last(),
|
||||
parameter_type.data_type());
|
||||
}
|
||||
|
||||
return input_variables;
|
||||
}
|
||||
|
||||
mf::Variable *MultiFunctionProcedureOperation::get_constant_input_variable(
|
||||
const bNodeSocket &input)
|
||||
{
|
||||
const mf::MultiFunction *constant_function = nullptr;
|
||||
switch (input.type) {
|
||||
case SOCK_FLOAT: {
|
||||
const float value = input.default_value_typed<bNodeSocketValueFloat>()->value;
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float>>(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_INT: {
|
||||
const int value = input.default_value_typed<bNodeSocketValueInt>()->value;
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<int32_t>>(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_BOOLEAN: {
|
||||
const bool value = input.default_value_typed<bNodeSocketValueBoolean>()->value;
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<bool>>(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_VECTOR: {
|
||||
switch (input.default_value_typed<bNodeSocketValueVector>()->dimensions) {
|
||||
case 2: {
|
||||
const float2 value = float2(input.default_value_typed<bNodeSocketValueVector>()->value);
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float2>>(value);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
const float3 value = float3(input.default_value_typed<bNodeSocketValueVector>()->value);
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float3>>(value);
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
const float4 value = float4(input.default_value_typed<bNodeSocketValueVector>()->value);
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float4>>(value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_INT_VECTOR: {
|
||||
switch (input.default_value_typed<bNodeSocketValueIntVector>()->dimensions) {
|
||||
case 2: {
|
||||
const int2 value = int2(input.default_value_typed<bNodeSocketValueIntVector>()->value);
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<int2>>(value);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
const int3 value = int3(input.default_value_typed<bNodeSocketValueIntVector>()->value);
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<int3>>(value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_RGBA: {
|
||||
const Color value = Color(input.default_value_typed<bNodeSocketValueRGBA>()->value);
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float4>>(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_MATRIX: {
|
||||
const float4x4 value = float4x4::identity();
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float4x4>>(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_MENU: {
|
||||
const int32_t value = input.default_value_typed<bNodeSocketValueMenu>()->value;
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<nodes::MenuValue>>(
|
||||
value);
|
||||
break;
|
||||
}
|
||||
case SOCK_STRING: {
|
||||
const std::string value = input.default_value_typed<bNodeSocketValueString>()->value;
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<std::string>>(
|
||||
value);
|
||||
break;
|
||||
}
|
||||
case SOCK_ROTATION: {
|
||||
const bNodeSocketValueRotation *rotation =
|
||||
input.default_value_typed<bNodeSocketValueRotation>();
|
||||
const math::EulerXYZ euler(float3(rotation->value_euler));
|
||||
const math::Quaternion value = math::to_quaternion(euler);
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<math::Quaternion>>(
|
||||
value);
|
||||
break;
|
||||
}
|
||||
case SOCK_OBJECT: {
|
||||
Object *value = input.default_value_typed<bNodeSocketValueObject>()->value;
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<Object *>>(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_IMAGE: {
|
||||
Image *value = input.default_value_typed<bNodeSocketValueImage>()->value;
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<Image *>>(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_FONT: {
|
||||
VFont *value = input.default_value_typed<bNodeSocketValueFont>()->value;
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<VFont *>>(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_SCENE: {
|
||||
Scene *value = input.default_value_typed<bNodeSocketValueScene>()->value;
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<Scene *>>(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_TEXT_ID: {
|
||||
Text *value = input.default_value_typed<bNodeSocketValueText>()->value;
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<Text *>>(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_MASK: {
|
||||
Mask *value = input.default_value_typed<bNodeSocketValueMask>()->value;
|
||||
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<Mask *>>(value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
|
||||
mf::Variable *constant_variable = procedure_builder_.add_call<1>(*constant_function)[0];
|
||||
implicit_variables_.append(constant_variable);
|
||||
return constant_variable;
|
||||
}
|
||||
|
||||
mf::Variable *MultiFunctionProcedureOperation::get_implicit_input_variable(
|
||||
const bNodeSocket &input)
|
||||
{
|
||||
const InputDescriptor input_descriptor = input_descriptor_from_input_socket(&input);
|
||||
const ImplicitInputType implicit_input = input_descriptor.implicit_input.value();
|
||||
|
||||
/* An input was already declared for that implicit input, so no need to declare it again and we
|
||||
* just return its variable. */
|
||||
if (implicit_input_to_variable_map_.contains(implicit_input)) {
|
||||
/* But first we update the domain priority of the input descriptor to be the higher priority of
|
||||
* the existing descriptor and the descriptor of the new input socket. That's because the same
|
||||
* implicit input might be used in inputs inside the multi-function procedure operation which
|
||||
* have different priorities. */
|
||||
InputDescriptor &existing_input_descriptor = this->get_input_descriptor(
|
||||
implicit_inputs_to_input_identifiers_map_.lookup(implicit_input));
|
||||
existing_input_descriptor.domain_priority = math::min(
|
||||
existing_input_descriptor.domain_priority, input_descriptor.domain_priority);
|
||||
|
||||
return implicit_input_to_variable_map_.lookup(implicit_input);
|
||||
}
|
||||
|
||||
const int implicit_input_index = implicit_inputs_to_input_identifiers_map_.size();
|
||||
const std::string input_identifier = "implicit_input" + std::to_string(implicit_input_index);
|
||||
declare_input_descriptor(input_identifier, input_descriptor);
|
||||
|
||||
/* Map the implicit input to the identifier of the operation input that was declared for it. */
|
||||
implicit_inputs_to_input_identifiers_map_.add_new(implicit_input, input_identifier);
|
||||
|
||||
mf::Variable &variable = procedure_builder_.add_input_parameter(
|
||||
mf::DataType::ForSingle(Result::cpp_type(input_descriptor.type)), input_identifier);
|
||||
parameter_identifiers_.append(input_identifier);
|
||||
|
||||
/* Map the implicit input to the variable that was created for it. */
|
||||
implicit_input_to_variable_map_.add(implicit_input, &variable);
|
||||
|
||||
return &variable;
|
||||
}
|
||||
|
||||
mf::Variable *MultiFunctionProcedureOperation::get_multi_function_input_variable(
|
||||
const bNodeSocket &input_socket, const bNodeSocket &output_socket)
|
||||
{
|
||||
/* An input was already declared for that same output socket, so no need to declare it again and
|
||||
* we just return its variable. */
|
||||
if (output_to_variable_map_.contains(&output_socket)) {
|
||||
/* But first we update the domain priority of the input descriptor to be the higher priority of
|
||||
* the existing descriptor and the descriptor of the new input socket. That's because the same
|
||||
* output might be connected to multiple inputs inside the multi-function procedure operation
|
||||
* which have different priorities. */
|
||||
const std::string input_identifier = outputs_to_declared_inputs_map_.lookup(&output_socket);
|
||||
InputDescriptor &input_descriptor = this->get_input_descriptor(input_identifier);
|
||||
input_descriptor.domain_priority = math::min(
|
||||
input_descriptor.domain_priority,
|
||||
input_descriptor_from_input_socket(&input_socket).domain_priority);
|
||||
|
||||
/* Increment the input's reference count. */
|
||||
inputs_to_reference_counts_map_.lookup(input_identifier)++;
|
||||
|
||||
return output_to_variable_map_.lookup(&output_socket);
|
||||
}
|
||||
|
||||
const int input_index = inputs_to_linked_outputs_map_.size();
|
||||
const std::string input_identifier = "input" + std::to_string(input_index);
|
||||
|
||||
/* Declare the input descriptor for this input and prefer to declare its type to be the same as
|
||||
* the type of the output socket because doing type conversion in the multi-function procedure is
|
||||
* cheaper. */
|
||||
InputDescriptor input_descriptor = input_descriptor_from_input_socket(&input_socket);
|
||||
input_descriptor.type = get_node_socket_result_type(&output_socket);
|
||||
declare_input_descriptor(input_identifier, input_descriptor);
|
||||
|
||||
mf::Variable &variable = procedure_builder_.add_input_parameter(
|
||||
mf::DataType::ForSingle(Result::cpp_type(input_descriptor.type)), input_identifier);
|
||||
parameter_identifiers_.append(input_identifier);
|
||||
|
||||
/* Map the output socket to the variable that was created for it. */
|
||||
output_to_variable_map_.add(&output_socket, &variable);
|
||||
|
||||
/* Map the identifier of the operation input to the output socket it is linked to. */
|
||||
inputs_to_linked_outputs_map_.add_new(input_identifier, &output_socket);
|
||||
|
||||
/* Map the output socket to the identifier of the operation input that was declared for it. */
|
||||
outputs_to_declared_inputs_map_.add_new(&output_socket, input_identifier);
|
||||
|
||||
/* Map the identifier of the operation input to a reference count of 1, this will later be
|
||||
* incremented if that same output was referenced again. */
|
||||
inputs_to_reference_counts_map_.add_new(input_identifier, 1);
|
||||
|
||||
return &variable;
|
||||
}
|
||||
|
||||
void MultiFunctionProcedureOperation::assign_output_variables(const bNode &node,
|
||||
Vector<mf::Variable *> &variables)
|
||||
{
|
||||
const bool should_log_outputs = this->context().nodes_evaluation_log() && is_single_value_;
|
||||
const bNodeSocket *preview_output = needs_node_previews_ && !is_single_value_ ?
|
||||
find_preview_output_socket(node) :
|
||||
nullptr;
|
||||
|
||||
int available_outputs_index = 0;
|
||||
for (const bNodeSocket *output : node.output_sockets()) {
|
||||
if (!is_socket_available(output)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mf::Variable *output_variable = variables[available_outputs_index];
|
||||
output_to_variable_map_.add_new(output, output_variable);
|
||||
|
||||
/* If any of the nodes linked to the output are not part of the multi-function procedure
|
||||
* operation but are part of the execution schedule, then an output result needs to be
|
||||
* populated for it. */
|
||||
const bool is_operation_output = is_output_linked_to_input_conditioned(
|
||||
*output, [&](const bNodeSocket &input) {
|
||||
return schedule_.nodes.contains(&input.owner_node()) &&
|
||||
!schedule_.unneeded_inputs.contains(&input) &&
|
||||
!compile_unit_.contains(&input.owner_node());
|
||||
});
|
||||
|
||||
/* If the output is used as the node preview, then an output result needs to be populated for
|
||||
* it, and we additionally keep track of that output to later compute the previews from. */
|
||||
const bool is_preview_output = output == preview_output;
|
||||
if (is_preview_output) {
|
||||
preview_outputs_.add(output);
|
||||
}
|
||||
|
||||
if (should_log_outputs) {
|
||||
logged_outputs_.add(output);
|
||||
}
|
||||
|
||||
if (is_operation_output || is_preview_output || should_log_outputs) {
|
||||
this->populate_operation_result(*output, output_variable);
|
||||
}
|
||||
|
||||
available_outputs_index++;
|
||||
}
|
||||
}
|
||||
|
||||
void MultiFunctionProcedureOperation::populate_operation_result(const bNodeSocket &output_socket,
|
||||
mf::Variable *variable)
|
||||
{
|
||||
const uint output_id = output_sockets_to_output_identifiers_map_.size();
|
||||
const std::string output_identifier = "output" + std::to_string(output_id);
|
||||
|
||||
const ResultType result_type = get_node_socket_result_type(&output_socket);
|
||||
populate_result(output_identifier, result_type);
|
||||
|
||||
/* Map the output socket to the identifier of the newly populated result. */
|
||||
output_sockets_to_output_identifiers_map_.add_new(&output_socket, output_identifier);
|
||||
|
||||
/* Implicitly convert the variable type to the expected result type if needed. */
|
||||
const mf::DataType expected_type = mf::DataType::ForSingle(Result::cpp_type(result_type));
|
||||
mf::Variable *converted_variable = this->convert_variable(variable, expected_type);
|
||||
|
||||
procedure_builder_.add_output_parameter(*converted_variable);
|
||||
output_variables_.add_new(converted_variable);
|
||||
parameter_identifiers_.append(output_identifier);
|
||||
}
|
||||
|
||||
mf::Variable *MultiFunctionProcedureOperation::convert_variable(mf::Variable *variable,
|
||||
const mf::DataType expected_type)
|
||||
{
|
||||
/* Conversion not needed. */
|
||||
const mf::DataType variable_type = variable->data_type();
|
||||
if (variable_type == expected_type) {
|
||||
return variable;
|
||||
}
|
||||
|
||||
const bke::DataTypeConversions &conversion_table = bke::get_implicit_type_conversions();
|
||||
const mf::MultiFunction *function = conversion_table.get_conversion_multi_function(
|
||||
variable_type, expected_type);
|
||||
|
||||
/* Conversion is not possible, return a default variable instead. */
|
||||
if (!function) {
|
||||
return this->get_default_value_variable(expected_type);
|
||||
}
|
||||
|
||||
mf::Variable *converted_variable = procedure_builder_.add_call<1>(*function, {variable})[0];
|
||||
implicit_variables_.append(converted_variable);
|
||||
return converted_variable;
|
||||
}
|
||||
|
||||
mf::Variable *MultiFunctionProcedureOperation::get_default_value_variable(const mf::DataType type)
|
||||
{
|
||||
const mf::MultiFunction &constant_function =
|
||||
procedure_.construct_function<mf::CustomMF_GenericConstant>(
|
||||
type.single_type(), type.single_type().default_value(), false);
|
||||
mf::Variable *constant_variable = procedure_builder_.add_call<1>(constant_function)[0];
|
||||
implicit_variables_.append(constant_variable);
|
||||
return constant_variable;
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
@@ -0,0 +1,317 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_compute_context.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "NOD_eval_log.hh"
|
||||
|
||||
#include "COM_compile_state.hh"
|
||||
#include "COM_context.hh"
|
||||
#include "COM_group_input_node_operation.hh"
|
||||
#include "COM_group_node_operation.hh"
|
||||
#include "COM_group_output_node_operation.hh"
|
||||
#include "COM_implicit_input_operation.hh"
|
||||
#include "COM_input_descriptor.hh"
|
||||
#include "COM_multi_function_procedure_operation.hh"
|
||||
#include "COM_node_group_operation.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_operation.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_scheduler.hh"
|
||||
#include "COM_shader_operation.hh"
|
||||
#include "COM_single_value_node_input_operation.hh"
|
||||
#include "COM_undefined_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
NodeGroupOperation::NodeGroupOperation(Context &context,
|
||||
const bNodeTree &node_group,
|
||||
const NodeGroupOutputTypes needed_outputs,
|
||||
const bNodeInstanceKey active_node_group_instance_key,
|
||||
const bNodeInstanceKey instance_key,
|
||||
const ComputeContext &compute_context)
|
||||
: Operation(context),
|
||||
node_group_(node_group),
|
||||
needed_output_types_(needed_outputs),
|
||||
active_node_group_instance_key_(active_node_group_instance_key),
|
||||
instance_key_(instance_key),
|
||||
compute_context_(compute_context)
|
||||
{
|
||||
node_group.ensure_interface_cache();
|
||||
for (const bNodeTreeInterfaceSocket *input : node_group.interface_inputs()) {
|
||||
const InputDescriptor input_descriptor = input_descriptor_from_interface_input(node_group,
|
||||
*input);
|
||||
this->declare_input_descriptor(input->identifier, input_descriptor);
|
||||
}
|
||||
|
||||
for (const bNodeTreeInterfaceSocket *output : node_group.interface_outputs()) {
|
||||
this->populate_result(output->identifier, get_node_interface_socket_result_type(*output));
|
||||
}
|
||||
}
|
||||
|
||||
class ScopedNodeGroupTimer {
|
||||
private:
|
||||
const ComputeContext &compute_context_;
|
||||
nodes::eval_log::NodesEvalLog *log_;
|
||||
|
||||
nodes::eval_log::TimePoint start_;
|
||||
|
||||
public:
|
||||
ScopedNodeGroupTimer(const ComputeContext &compute_context, nodes::eval_log::NodesEvalLog *log)
|
||||
: compute_context_(compute_context), log_(log)
|
||||
{
|
||||
start_ = nodes::eval_log::Clock::now();
|
||||
}
|
||||
|
||||
~ScopedNodeGroupTimer()
|
||||
{
|
||||
if (!log_) {
|
||||
return;
|
||||
}
|
||||
const nodes::eval_log::TimePoint end = nodes::eval_log::Clock::now();
|
||||
nodes::eval_log::NodeTreeLogger &tree_logger = log_->get_local_tree_logger(compute_context_);
|
||||
tree_logger.execution_time = end - start_;
|
||||
}
|
||||
};
|
||||
|
||||
void NodeGroupOperation::execute()
|
||||
{
|
||||
const ScopedNodeGroupTimer node_group_timer{compute_context_,
|
||||
this->context().nodes_evaluation_log()};
|
||||
const Schedule schedule = compute_schedule(this->context(),
|
||||
node_group_,
|
||||
*this,
|
||||
needed_output_types_,
|
||||
instance_key_,
|
||||
active_node_group_instance_key_);
|
||||
CompileState compile_state(this->context(), schedule);
|
||||
|
||||
for (const bNode *node : schedule.nodes) {
|
||||
if (this->context().is_canceled()) {
|
||||
this->cancel_evaluation();
|
||||
break;
|
||||
}
|
||||
|
||||
if (compile_state.should_compile_pixel_compile_unit(*node)) {
|
||||
this->evaluate_pixel_compile_unit(compile_state);
|
||||
}
|
||||
|
||||
if (is_pixel_node(*node)) {
|
||||
compile_state.add_node_to_pixel_compile_unit(*node);
|
||||
}
|
||||
else {
|
||||
this->evaluate_node(*node, compile_state);
|
||||
}
|
||||
}
|
||||
|
||||
/* Some of the needed outputs might not be allocated even after execution. This could happen for
|
||||
* instance when no Group Output node exist or when the evaluation gets canceled before the
|
||||
* output is written. */
|
||||
this->allocate_default_remaining_outputs();
|
||||
}
|
||||
|
||||
void NodeGroupOperation::evaluate_node(const bNode &node, CompileState &compile_state)
|
||||
{
|
||||
NodeOperation *operation = this->get_node_operation(node);
|
||||
operation->set_instance_key(bke::node_instance_key(instance_key_, &node_group_, &node));
|
||||
operation->set_compute_context(compute_context_);
|
||||
|
||||
/* Only compute previews if the node group is currently being viewed. */
|
||||
operation->set_needs_node_previews(
|
||||
bool(needed_output_types_ & NodeGroupOutputTypes::NodePreviews) &&
|
||||
instance_key_ == active_node_group_instance_key_);
|
||||
|
||||
compile_state.map_node_to_node_operation(node, operation);
|
||||
|
||||
map_node_operation_inputs_to_their_results(node, operation, compile_state);
|
||||
|
||||
/* This has to be done after input mapping because the method may add Input Single Value
|
||||
* Operations to the operations stream, which needs to be evaluated before the operation itself
|
||||
* is evaluated. */
|
||||
operations_stream_.append(std::unique_ptr<Operation>(operation));
|
||||
|
||||
operation->compute_results_reference_counts(compile_state.get_schedule());
|
||||
|
||||
operation->evaluate();
|
||||
}
|
||||
|
||||
NodeOperation *NodeGroupOperation::get_node_operation(const bNode &node)
|
||||
{
|
||||
const char *disabled_hint = nullptr;
|
||||
if (!node.typeinfo->poll(node.typeinfo, &node.owner_tree(), &disabled_hint)) {
|
||||
return get_undefined_node_operation(this->context(), node);
|
||||
}
|
||||
|
||||
if (node.is_group()) {
|
||||
return get_group_node_operation(
|
||||
this->context(), node, needed_output_types_, active_node_group_instance_key_);
|
||||
}
|
||||
|
||||
if (node.is_group_output()) {
|
||||
return get_group_output_node_operation(this->context(), node, *this);
|
||||
}
|
||||
|
||||
if (node.is_group_input()) {
|
||||
return get_group_input_node_operation(this->context(), node, *this);
|
||||
}
|
||||
|
||||
return node.typeinfo->get_compositor_operation(this->context(), node);
|
||||
}
|
||||
|
||||
void NodeGroupOperation::map_node_operation_inputs_to_their_results(const bNode &node,
|
||||
NodeOperation *operation,
|
||||
CompileState &compile_state)
|
||||
{
|
||||
for (const bNodeSocket *input : node.input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bNodeSocket *output = get_output_linked_to_input(*input);
|
||||
if (output && compile_state.get_schedule().nodes.contains(&output->owner_node()) &&
|
||||
!compile_state.get_schedule().unneeded_inputs.contains(input))
|
||||
{
|
||||
/* The input is linked to a node that is part of the schedule. So map the input to the result
|
||||
* we get from the output. */
|
||||
Result &result = compile_state.get_result_from_output_socket(*output);
|
||||
operation->map_input_to_result(input->identifier, &result);
|
||||
continue;
|
||||
}
|
||||
|
||||
const InputDescriptor input_descriptor = input_descriptor_from_input_socket(input);
|
||||
if (!input_descriptor.implicit_input.has_value()) {
|
||||
/* The input is unlinked with no implicit value. So map the input to the result of a newly
|
||||
* created Input Single Value Operation. */
|
||||
SingleValueNodeInputOperation *input_operation = new SingleValueNodeInputOperation(
|
||||
this->context(), *input);
|
||||
operations_stream_.append(std::unique_ptr<SingleValueNodeInputOperation>(input_operation));
|
||||
input_operation->evaluate();
|
||||
operation->map_input_to_result(input->identifier, &input_operation->get_result());
|
||||
continue;
|
||||
}
|
||||
|
||||
ImplicitInputOperation *input_operation = new ImplicitInputOperation(
|
||||
this->context(), input_descriptor.implicit_input.value());
|
||||
operations_stream_.append(std::unique_ptr<ImplicitInputOperation>(input_operation));
|
||||
input_operation->evaluate();
|
||||
operation->map_input_to_result(input->identifier, &input_operation->get_result());
|
||||
}
|
||||
}
|
||||
|
||||
/* Create one of the concrete subclasses of the PixelOperation based on the context and compile
|
||||
* state. Deleting the operation is the caller's responsibility. */
|
||||
static PixelOperation *create_pixel_operation(Context &context,
|
||||
CompileState &compile_state,
|
||||
const ComputeContext &compute_context)
|
||||
{
|
||||
const Schedule &schedule = compile_state.get_schedule();
|
||||
PixelCompileUnit &compile_unit = compile_state.get_pixel_compile_unit();
|
||||
|
||||
/* Use multi-function procedure to execute the pixel compile unit for CPU contexts or if the
|
||||
* compile unit is single value and would thus be more efficient to execute on the CPU. */
|
||||
const bool is_single_value = compile_state.is_pixel_compile_unit_single_value();
|
||||
if (!context.use_gpu() || is_single_value) {
|
||||
return new MultiFunctionProcedureOperation(
|
||||
context, compile_unit, schedule, is_single_value, compute_context);
|
||||
}
|
||||
|
||||
return new ShaderOperation(context, compile_unit, schedule, compute_context);
|
||||
}
|
||||
|
||||
void NodeGroupOperation::evaluate_pixel_compile_unit(CompileState &compile_state)
|
||||
{
|
||||
PixelCompileUnit &compile_unit = compile_state.get_pixel_compile_unit();
|
||||
|
||||
/* Pixel operations might have limitations on the number of outputs or inputs they can have, so
|
||||
* we might have to split the compile unit into smaller units to workaround this limitation. In
|
||||
* practice, splitting will almost always never happen due to the scheduling strategy we use, so
|
||||
* the base case remains fast. */
|
||||
const bool are_node_previews_needed = instance_key_ == active_node_group_instance_key_;
|
||||
if (compile_state.pixel_compile_unit_has_too_many_outputs(are_node_previews_needed) ||
|
||||
compile_state.pixel_compile_unit_has_too_many_inputs())
|
||||
{
|
||||
const int split_index = compile_unit.size() / 2;
|
||||
const PixelCompileUnit start_compile_unit(compile_unit.as_span().take_front(split_index));
|
||||
const PixelCompileUnit end_compile_unit(compile_unit.as_span().drop_front(split_index));
|
||||
|
||||
compile_state.get_pixel_compile_unit() = start_compile_unit;
|
||||
this->evaluate_pixel_compile_unit(compile_state);
|
||||
|
||||
compile_state.get_pixel_compile_unit() = end_compile_unit;
|
||||
this->evaluate_pixel_compile_unit(compile_state);
|
||||
|
||||
/* No need to continue, the above recursive calls will eventually exist the loop and do the
|
||||
* actual compilation. */
|
||||
return;
|
||||
}
|
||||
|
||||
PixelOperation *operation = create_pixel_operation(
|
||||
this->context(), compile_state, compute_context_);
|
||||
|
||||
/* Only compute previews if the node group is currently being viewed. */
|
||||
operation->set_needs_node_previews(
|
||||
bool(needed_output_types_ & NodeGroupOutputTypes::NodePreviews) &&
|
||||
instance_key_ == active_node_group_instance_key_);
|
||||
|
||||
for (const bNode *node : compile_unit) {
|
||||
compile_state.map_node_to_pixel_operation(*node, operation);
|
||||
}
|
||||
|
||||
map_pixel_operation_inputs_to_their_results(operation, compile_state);
|
||||
|
||||
operations_stream_.append(std::unique_ptr<Operation>(operation));
|
||||
|
||||
operation->compute_results_reference_counts(compile_state.get_schedule());
|
||||
|
||||
operation->evaluate();
|
||||
|
||||
compile_state.reset_pixel_compile_unit();
|
||||
}
|
||||
|
||||
void NodeGroupOperation::map_pixel_operation_inputs_to_their_results(PixelOperation *operation,
|
||||
CompileState &compile_state)
|
||||
{
|
||||
for (const auto item : operation->get_inputs_to_linked_outputs_map().items()) {
|
||||
const bNodeSocket &output = *item.value;
|
||||
const StringRef input_identifier = item.key;
|
||||
|
||||
Result *input_result = &compile_state.get_result_from_output_socket(output);
|
||||
operation->map_input_to_result(input_identifier, input_result);
|
||||
|
||||
/* Correct the reference count of the result in case multiple of the result's outgoing links
|
||||
* corresponds to a single input in the pixel operation. See the description of the member
|
||||
* inputs_to_reference_counts_map_ variable for more information. */
|
||||
const int internal_reference_count = operation->get_internal_input_reference_count(
|
||||
input_identifier);
|
||||
input_result->decrement_reference_count(internal_reference_count - 1);
|
||||
}
|
||||
|
||||
for (const auto item : operation->get_implicit_inputs_to_input_identifiers_map().items()) {
|
||||
ImplicitInputOperation *input_operation = new ImplicitInputOperation(this->context(),
|
||||
item.key);
|
||||
operation->map_input_to_result(item.value, &input_operation->get_result());
|
||||
|
||||
operations_stream_.append(std::unique_ptr<ImplicitInputOperation>(input_operation));
|
||||
|
||||
input_operation->evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeGroupOperation::cancel_evaluation()
|
||||
{
|
||||
for (const std::unique_ptr<Operation> &operation : operations_stream_) {
|
||||
operation->free_results();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
251
blender-5.2.0/source/blender/compositor/intern/node_operation.cc
Normal file
251
blender-5.2.0/source/blender/compositor/intern/node_operation.cc
Normal file
@@ -0,0 +1,251 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "GPU_debug.hh"
|
||||
|
||||
#include "NOD_eval_log.hh"
|
||||
|
||||
#include "COM_algorithm_compute_preview.hh"
|
||||
#include "COM_context.hh"
|
||||
#include "COM_input_descriptor.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_operation.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_scheduler.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
NodeOperation::NodeOperation(Context &context, const bNode &node) : Operation(context), node_(node)
|
||||
{
|
||||
for (const bNodeSocket *output : this->node().output_sockets()) {
|
||||
if (!is_socket_available(output)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
populate_result(output->identifier, get_node_socket_result_type(output));
|
||||
}
|
||||
|
||||
for (const bNodeSocket *input : this->node().input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const InputDescriptor input_descriptor = input_descriptor_from_input_socket(input);
|
||||
declare_input_descriptor(input->identifier, input_descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
class ScopedNodeTimer {
|
||||
private:
|
||||
const bNode &node_;
|
||||
const ComputeContext &compute_context_;
|
||||
nodes::eval_log::NodesEvalLog *log_;
|
||||
|
||||
nodes::eval_log::TimePoint start_;
|
||||
|
||||
public:
|
||||
ScopedNodeTimer(const bNode &node,
|
||||
const ComputeContext &compute_context,
|
||||
nodes::eval_log::NodesEvalLog *log)
|
||||
: node_(node), compute_context_(compute_context), log_(log)
|
||||
{
|
||||
start_ = nodes::eval_log::Clock::now();
|
||||
}
|
||||
|
||||
~ScopedNodeTimer()
|
||||
{
|
||||
if (!log_) {
|
||||
return;
|
||||
}
|
||||
const nodes::eval_log::TimePoint end = nodes::eval_log::Clock::now();
|
||||
nodes::eval_log::NodeTreeLogger &tree_logger = log_->get_local_tree_logger(compute_context_);
|
||||
tree_logger.node_execution_times.append(*tree_logger.allocator,
|
||||
{node_.identifier, start_, end});
|
||||
}
|
||||
};
|
||||
|
||||
void NodeOperation::evaluate()
|
||||
{
|
||||
const ScopedNodeTimer node_timer{
|
||||
this->node(), this->get_compute_context(), this->context().nodes_evaluation_log()};
|
||||
if (this->context().use_gpu()) {
|
||||
GPU_debug_group_begin(this->node().typeinfo->idname.c_str());
|
||||
}
|
||||
Operation::evaluate();
|
||||
if (this->context().use_gpu()) {
|
||||
GPU_debug_group_end();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeOperation::compute_results_reference_counts(const Schedule &schedule)
|
||||
{
|
||||
for (const bNodeSocket *output : this->node().output_sockets()) {
|
||||
if (!is_socket_available(output)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int reference_count = number_of_inputs_linked_to_output_conditioned(
|
||||
*output, [&](const bNodeSocket &input) {
|
||||
return schedule.nodes.contains(&input.owner_node()) &&
|
||||
!schedule.unneeded_inputs.contains(&input);
|
||||
});
|
||||
|
||||
this->get_result(output->identifier).set_reference_count(reference_count);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeOperation::set_instance_key(const bNodeInstanceKey &instance_key)
|
||||
{
|
||||
instance_key_ = instance_key;
|
||||
}
|
||||
|
||||
const bNodeInstanceKey &NodeOperation::get_instance_key() const
|
||||
{
|
||||
return instance_key_;
|
||||
}
|
||||
|
||||
void NodeOperation::set_compute_context(const ComputeContext &compute_context)
|
||||
{
|
||||
compute_context_ = &compute_context;
|
||||
}
|
||||
|
||||
const ComputeContext &NodeOperation::get_compute_context() const
|
||||
{
|
||||
return *compute_context_;
|
||||
}
|
||||
|
||||
void NodeOperation::set_needs_node_previews(const bool needed)
|
||||
{
|
||||
needs_node_previews_ = needed;
|
||||
}
|
||||
|
||||
static destruct_ptr<nodes::eval_log::ImageInfoLog> get_image_info_log(LinearAllocator<> *allocator,
|
||||
const Result &result)
|
||||
{
|
||||
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_);
|
||||
|
||||
/* Log input values. */
|
||||
for (const bNodeSocket *input_socket : this->node().input_sockets()) {
|
||||
if (!is_socket_available(input_socket)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const InputDescriptor &input_descriptor = this->get_input_descriptor(input_socket->identifier);
|
||||
if (!input_socket->is_logically_linked() && !input_descriptor.implicit_input.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Result &input = this->get_input(input_socket->identifier);
|
||||
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)});
|
||||
}
|
||||
|
||||
/* 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
|
||||
{
|
||||
return node_;
|
||||
}
|
||||
|
||||
Result *NodeOperation::get_preview_result()
|
||||
{
|
||||
/* Find the first linked output. */
|
||||
for (const bNodeSocket *output : this->node().output_sockets()) {
|
||||
if (!is_socket_available(output)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Result &output_result = this->get_result(output->identifier);
|
||||
if (output_result.should_compute()) {
|
||||
return &output_result;
|
||||
}
|
||||
}
|
||||
|
||||
/* No linked outputs, but no inputs either, so nothing to preview. */
|
||||
if (this->node().input_sockets().is_empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Find the first allocated input. */
|
||||
for (const bNodeSocket *input : this->node().input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Result &input_result = this->get_input(input->identifier);
|
||||
if (input_result.is_allocated()) {
|
||||
return &input_result;
|
||||
}
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
179
blender-5.2.0/source/blender/compositor/intern/operation.cc
Normal file
179
blender-5.2.0/source/blender/compositor/intern/operation.cc
Normal file
@@ -0,0 +1,179 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include "COM_context.hh"
|
||||
#include "COM_conversion_operation.hh"
|
||||
#include "COM_domain.hh"
|
||||
#include "COM_input_descriptor.hh"
|
||||
#include "COM_operation.hh"
|
||||
#include "COM_realize_on_domain_operation.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_simple_operation.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
Operation::Operation(Context &context) : context_(context) {}
|
||||
|
||||
Operation::~Operation() = default;
|
||||
|
||||
void Operation::evaluate()
|
||||
{
|
||||
this->evaluate_input_processors();
|
||||
this->execute();
|
||||
this->log_data();
|
||||
this->release_inputs();
|
||||
this->context().evaluate_operation_post();
|
||||
}
|
||||
|
||||
Result &Operation::get_input(StringRef identifier) const
|
||||
{
|
||||
return *results_mapped_to_inputs_.lookup(identifier);
|
||||
}
|
||||
|
||||
Result &Operation::get_result(StringRef identifier)
|
||||
{
|
||||
return results_.lookup(identifier);
|
||||
}
|
||||
|
||||
void Operation::map_input_to_result(StringRef identifier, Result *result)
|
||||
{
|
||||
results_mapped_to_inputs_.add_new(identifier, result);
|
||||
}
|
||||
|
||||
void Operation::free_results()
|
||||
{
|
||||
for (Result &result : results_.values()) {
|
||||
result.free();
|
||||
}
|
||||
}
|
||||
|
||||
Domain Operation::compute_domain()
|
||||
{
|
||||
/* Default to an identity domain in case no domain input was found, most likely because all
|
||||
* inputs are single values. */
|
||||
Domain operation_domain = Domain::identity();
|
||||
int current_domain_priority = std::numeric_limits<int>::max();
|
||||
|
||||
/* Go over the inputs and find the domain of the non single value input with the highest domain
|
||||
* priority. */
|
||||
for (StringRef identifier : input_descriptors_.keys()) {
|
||||
const Result &result = get_input(identifier);
|
||||
const InputDescriptor &descriptor = get_input_descriptor(identifier);
|
||||
|
||||
/* A single value input can't be a domain input. */
|
||||
if (result.is_single_value() || descriptor.expects_single_value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* An input that skips operation domain realization can't be a domain input. */
|
||||
if (descriptor.realization_mode != InputRealizationMode::OperationDomain) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Notice that the lower the domain priority value is, the higher the priority is, hence the
|
||||
* less than comparison. */
|
||||
if (descriptor.domain_priority < current_domain_priority) {
|
||||
operation_domain = result.domain();
|
||||
current_domain_priority = descriptor.domain_priority;
|
||||
}
|
||||
}
|
||||
|
||||
return operation_domain;
|
||||
}
|
||||
|
||||
void Operation::evaluate_input_processors()
|
||||
{
|
||||
/* Each input processor type is added to all inputs entirely before the next type. This is done
|
||||
* because the construction of the input processors may depend on the result of previous input
|
||||
* processors for all inputs. For instance, the realize on domain input processor considers the
|
||||
* value of all inputs, so previous input processors for all inputs needs to be added and
|
||||
* evaluated first. */
|
||||
|
||||
for (const StringRef &identifier : results_mapped_to_inputs_.keys()) {
|
||||
SimpleOperation *conversion = ConversionOperation::construct_if_needed(
|
||||
this->context(), this->get_input(identifier), this->get_input_descriptor(identifier));
|
||||
this->add_and_evaluate_input_processor(identifier, conversion);
|
||||
}
|
||||
|
||||
for (const StringRef &identifier : results_mapped_to_inputs_.keys()) {
|
||||
SimpleOperation *realize_on_domain = RealizeOnDomainOperation::construct_if_needed(
|
||||
this->context(),
|
||||
this->get_input(identifier),
|
||||
this->get_input_descriptor(identifier),
|
||||
this->compute_domain());
|
||||
this->add_and_evaluate_input_processor(identifier, realize_on_domain);
|
||||
}
|
||||
}
|
||||
|
||||
void Operation::log_data() {};
|
||||
|
||||
void Operation::populate_result(StringRef identifier, const ResultType type)
|
||||
{
|
||||
results_.add_new(identifier, this->context().create_result(type));
|
||||
}
|
||||
|
||||
void Operation::declare_input_descriptor(StringRef identifier, InputDescriptor descriptor)
|
||||
{
|
||||
input_descriptors_.add_new(identifier, descriptor);
|
||||
}
|
||||
|
||||
InputDescriptor &Operation::get_input_descriptor(StringRef identifier)
|
||||
{
|
||||
return input_descriptors_.lookup(identifier);
|
||||
}
|
||||
|
||||
void Operation::allocate_default_remaining_outputs()
|
||||
{
|
||||
for (Result &result : results_.values()) {
|
||||
if (result.should_compute() && !result.is_allocated()) {
|
||||
result.allocate_invalid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Context &Operation::context() const
|
||||
{
|
||||
return context_;
|
||||
}
|
||||
|
||||
void Operation::add_and_evaluate_input_processor(StringRef identifier, SimpleOperation *processor)
|
||||
{
|
||||
/* Allow null inputs to facilitate construct_if_needed pattern of addition. For instance, see the
|
||||
* implementation of the evaluate_input_processors method. */
|
||||
if (!processor) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessorsVector &processors = input_processors_.lookup_or_add_default(identifier);
|
||||
|
||||
/* Get the result that should serve as the input for the processor. This is either the result
|
||||
* mapped to the input or the result of the last processor depending on whether this is the first
|
||||
* processor or not. */
|
||||
Result &result = processors.is_empty() ? this->get_input(identifier) :
|
||||
processors.last()->get_result();
|
||||
|
||||
/* Map the input result of the processor and add it to the processors vector. */
|
||||
processor->map_input_to_result(&result);
|
||||
processors.append(std::unique_ptr<SimpleOperation>(processor));
|
||||
|
||||
/* Switch the result mapped to the input to be the output result of the processor. */
|
||||
results_mapped_to_inputs_.lookup(identifier) = &processor->get_result();
|
||||
|
||||
processor->evaluate();
|
||||
}
|
||||
|
||||
void Operation::release_inputs()
|
||||
{
|
||||
for (Result *result : results_mapped_to_inputs_.values()) {
|
||||
result->release();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
@@ -0,0 +1,218 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "NOD_eval_log.hh"
|
||||
|
||||
#include "COM_algorithm_compute_preview.hh"
|
||||
#include "COM_context.hh"
|
||||
#include "COM_operation.hh"
|
||||
#include "COM_pixel_operation.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_scheduler.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
PixelOperation::PixelOperation(Context &context,
|
||||
PixelCompileUnit &compile_unit,
|
||||
const Schedule &schedule,
|
||||
const ComputeContext &compute_context,
|
||||
const bool is_single_value)
|
||||
: Operation(context),
|
||||
compile_unit_(compile_unit),
|
||||
schedule_(schedule),
|
||||
compute_context_(compute_context),
|
||||
is_single_value_(is_single_value)
|
||||
{
|
||||
}
|
||||
|
||||
static destruct_ptr<nodes::eval_log::ImageInfoLog> get_image_info_log(
|
||||
LinearAllocator<> *allocator, const Domain &domain, const ResultPrecision &precision)
|
||||
{
|
||||
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(precision));
|
||||
}
|
||||
|
||||
void PixelOperation::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_);
|
||||
|
||||
if (is_single_value_) {
|
||||
for (const bNodeSocket *output_socket : logged_outputs_) {
|
||||
Result &result = this->get_result(
|
||||
this->get_output_identifier_from_output_socket(*output_socket));
|
||||
tree_logger.log_value(output_socket->owner_node(), *output_socket, result.single_value());
|
||||
|
||||
/* Logged results gets as an extra reference in pixel operations as can be seen in the
|
||||
* compute_results_reference_counts method, so release it after logging. */
|
||||
result.release();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const Domain domain = this->compute_domain();
|
||||
|
||||
/* All inputs and outputs of pixel operations operate in the same domain, so the operation domain
|
||||
* should be logged for all. The exception is inputs that are single values, in which case, their
|
||||
* value is simply logged. */
|
||||
for (const bNode *node : compile_unit_) {
|
||||
/* Log output values. */
|
||||
for (const bNodeSocket *output_socket : node->output_sockets()) {
|
||||
if (!is_socket_available(output_socket)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!output_socket->is_logically_linked()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tree_logger.output_socket_values.append(
|
||||
*tree_logger.allocator,
|
||||
{node->identifier,
|
||||
output_socket->index(),
|
||||
get_image_info_log(tree_logger.allocator, domain, this->context().get_precision())});
|
||||
}
|
||||
|
||||
/* Log input values. */
|
||||
for (const bNodeSocket *input_socket : node->input_sockets()) {
|
||||
if (!is_socket_available(input_socket)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The input has an implicit value. Get the input that corresponds to it, if it is a single
|
||||
* value, log that single value, if not, we log the operation domain. */
|
||||
const InputDescriptor input_descriptor = input_descriptor_from_input_socket(input_socket);
|
||||
if (!input_socket->is_logically_linked() && input_descriptor.implicit_input.has_value()) {
|
||||
const std::string &input_identifier = implicit_inputs_to_input_identifiers_map_.lookup(
|
||||
input_descriptor.implicit_input.value());
|
||||
const Result &input = this->get_input(input_identifier);
|
||||
if (input.is_single_value()) {
|
||||
tree_logger.log_value(*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, domain, this->context().get_precision())});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!input_socket->is_logically_linked()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The input is linked to a node that is inside the pixel operation, so skip it since it will
|
||||
* inherit its value from an output that was logged above. */
|
||||
const bNodeSocket &linked_output = *input_socket->logically_linked_sockets()[0];
|
||||
if (compile_unit_.contains(&linked_output.owner_node())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Otherwise, it is linked to a node that is outside of the compile unit. If it is a single
|
||||
* value, log that single value, if not, we log the operation domain. */
|
||||
const std::string &input_identifier = outputs_to_declared_inputs_map_.lookup(&linked_output);
|
||||
const Result &input = this->get_input(input_identifier);
|
||||
if (input.is_single_value()) {
|
||||
tree_logger.log_value(*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, domain, this->context().get_precision())});
|
||||
}
|
||||
}
|
||||
|
||||
for (const bNodeSocket *output : preview_outputs_) {
|
||||
Result &result = this->get_result(get_output_identifier_from_output_socket(*output));
|
||||
ImBuf *preview = compositor::compute_preview(context(), result);
|
||||
tree_logger.node_image_previews.append(*tree_logger.allocator,
|
||||
{output->owner_node().identifier, preview});
|
||||
|
||||
/* Preview results gets as an extra reference in pixel operations as can be seen in the
|
||||
* compute_results_reference_counts method, so release it after computing preview. */
|
||||
result.release();
|
||||
}
|
||||
}
|
||||
|
||||
StringRef PixelOperation::get_output_identifier_from_output_socket(
|
||||
const bNodeSocket &output_socket)
|
||||
{
|
||||
return output_sockets_to_output_identifiers_map_.lookup(&output_socket);
|
||||
}
|
||||
|
||||
Map<std::string, const bNodeSocket *> &PixelOperation::get_inputs_to_linked_outputs_map()
|
||||
{
|
||||
return inputs_to_linked_outputs_map_;
|
||||
}
|
||||
|
||||
Map<ImplicitInputType, std::string> &PixelOperation::get_implicit_inputs_to_input_identifiers_map()
|
||||
{
|
||||
return implicit_inputs_to_input_identifiers_map_;
|
||||
}
|
||||
|
||||
int PixelOperation::get_internal_input_reference_count(const StringRef &identifier)
|
||||
{
|
||||
return inputs_to_reference_counts_map_.lookup(identifier);
|
||||
}
|
||||
|
||||
void PixelOperation::compute_results_reference_counts(const Schedule &schedule)
|
||||
{
|
||||
for (const auto item : output_sockets_to_output_identifiers_map_.items()) {
|
||||
int reference_count = number_of_inputs_linked_to_output_conditioned(
|
||||
*item.key, [&](const bNodeSocket &input) {
|
||||
/* We only consider inputs that are not part of the pixel operations, because inputs
|
||||
* that are part of the pixel operations are internal and do not deal with the result
|
||||
* directly. */
|
||||
return schedule.nodes.contains(&input.owner_node()) &&
|
||||
!schedule.unneeded_inputs.contains(&input) &&
|
||||
!compile_unit_.contains(&input.owner_node());
|
||||
});
|
||||
|
||||
if (preview_outputs_.contains(item.key)) {
|
||||
reference_count++;
|
||||
}
|
||||
|
||||
if (logged_outputs_.contains(item.key)) {
|
||||
reference_count++;
|
||||
}
|
||||
|
||||
get_result(item.value).set_reference_count(reference_count);
|
||||
}
|
||||
}
|
||||
|
||||
void PixelOperation::set_needs_node_previews(const bool needed)
|
||||
{
|
||||
needs_node_previews_ = needed;
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
@@ -0,0 +1,328 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "GPU_shader.hh"
|
||||
#include "GPU_texture.hh"
|
||||
|
||||
#include "COM_context.hh"
|
||||
#include "COM_domain.hh"
|
||||
#include "COM_input_descriptor.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "COM_realize_on_domain_operation.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------
|
||||
* Realize On Domain Operation
|
||||
*/
|
||||
|
||||
RealizeOnDomainOperation::RealizeOnDomainOperation(Context &context,
|
||||
Domain target_domain,
|
||||
ResultType type)
|
||||
: SimpleOperation(context), target_domain_(target_domain)
|
||||
{
|
||||
InputDescriptor input_descriptor;
|
||||
input_descriptor.type = type;
|
||||
this->declare_input_descriptor(input_descriptor);
|
||||
this->populate_result(type);
|
||||
}
|
||||
|
||||
void RealizeOnDomainOperation::execute()
|
||||
{
|
||||
const Domain input_domain = this->get_input().domain();
|
||||
const Domain output_domain = target_domain_;
|
||||
|
||||
/* Create a transformation matrix that transforms the pixels in the data window from the data
|
||||
* space to the virtual compositing space. This is done by first adding the data offset to go
|
||||
* from the data space to the display space, then subtracting the center of the display window to
|
||||
* go from the display space to the virtual compositing space. See the corrective translation
|
||||
* function for more information on its function. */
|
||||
const float2 input_center = float2(input_domain.display_size) / 2.0f;
|
||||
const float2 input_translation = float2(input_domain.data_offset) - input_center +
|
||||
this->compute_corrective_translation();
|
||||
const float3x3 input_data_to_virtual = math::translate(input_domain.transformation,
|
||||
input_translation);
|
||||
|
||||
/* Same as above but for the output domain. */
|
||||
const float2 output_center = float2(output_domain.display_size) / 2.0f;
|
||||
const float2 output_translation = float2(output_domain.data_offset) - output_center;
|
||||
const float3x3 output_data_to_virtual = math::translate(output_domain.transformation,
|
||||
output_translation);
|
||||
|
||||
/* Create a transformation matrix from the output data space to the input data space */
|
||||
const float3x3 virtual_to_input_data = math::invert(input_data_to_virtual);
|
||||
const float3x3 output_data_to_input_data = virtual_to_input_data * output_data_to_virtual;
|
||||
|
||||
/* Create a transformation matrix from the output integer texel to the input normalized sampler
|
||||
* coordinates. This is done by adding 0.5 to evaluate the output at the center if pixels and
|
||||
* dividing by the input size to get normalized coordinates. */
|
||||
const float3x3 output_texel_to_output_data = math::from_location<float3x3>(float2(0.5f));
|
||||
const float3x3 input_data_to_input_sampler = math::from_scale<float3x3, 2>(
|
||||
1.0f / float2(input_domain.data_size));
|
||||
const float3x3 output_texel_to_input_sampler = input_data_to_input_sampler *
|
||||
output_data_to_input_data *
|
||||
output_texel_to_output_data;
|
||||
|
||||
if (this->context().use_gpu()) {
|
||||
this->realize_on_domain_gpu(output_texel_to_input_sampler);
|
||||
}
|
||||
else {
|
||||
this->realize_on_domain_cpu(output_texel_to_input_sampler);
|
||||
}
|
||||
}
|
||||
|
||||
float2 RealizeOnDomainOperation::compute_corrective_translation()
|
||||
{
|
||||
if (this->get_input().get_realization_options().interpolation == Interpolation::Nearest) {
|
||||
/* Bias translations in case of nearest interpolation to avoids the round-to-even behavior of
|
||||
* some GPUs at pixel boundaries. */
|
||||
return float2(std::numeric_limits<float>::epsilon() * 10e3f);
|
||||
}
|
||||
|
||||
/* Assuming no transformations, if the input size is odd and output size is even or vice versa,
|
||||
* the centers of pixels of the input and output will be half a pixel away from each other due
|
||||
* to the centering translation. Which introduce fuzzy result due to interpolation. So if one
|
||||
* is odd and the other is even, detected by testing the low bit of the xor of the sizes, shift
|
||||
* the input by 1/2 pixel so the pixels align. */
|
||||
const int2 output_size = this->compute_domain().data_size;
|
||||
const int2 input_size = this->get_input().domain().data_size;
|
||||
return float2(((input_size[0] ^ output_size[0]) & 1) ? -0.5f : 0.0f,
|
||||
((input_size[1] ^ output_size[1]) & 1) ? -0.5f : 0.0f);
|
||||
}
|
||||
|
||||
void RealizeOnDomainOperation::realize_on_domain_gpu(const float3x3 &transformation)
|
||||
{
|
||||
gpu::Shader *shader = this->context().get_shader(this->get_realization_shader_name());
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_mat3_as_mat4(shader, "transformation", transformation.ptr());
|
||||
|
||||
Result &input = this->get_input();
|
||||
const RealizationOptions realization_options = input.get_realization_options();
|
||||
|
||||
if (!GPU_texture_has_integer_format(input)) {
|
||||
/* The texture sampler should use bilinear interpolation for both the bilinear and bicubic
|
||||
* cases, as the logic used by the bicubic realization shader expects textures to use bilinear
|
||||
* interpolation. */
|
||||
if (realization_options.interpolation == Interpolation::Anisotropic) {
|
||||
GPU_texture_anisotropic_filter(input, true);
|
||||
GPU_texture_mipmap_mode(input, true, true);
|
||||
}
|
||||
else {
|
||||
GPU_texture_filter_mode(input, realization_options.interpolation != Interpolation::Nearest);
|
||||
}
|
||||
}
|
||||
|
||||
GPU_texture_extend_mode_x(input,
|
||||
map_extension_mode_to_extend_mode(realization_options.extension_x));
|
||||
GPU_texture_extend_mode_y(input,
|
||||
map_extension_mode_to_extend_mode(realization_options.extension_y));
|
||||
|
||||
input.bind_as_texture(shader, "input_tx");
|
||||
|
||||
const Domain domain = this->compute_domain();
|
||||
Result &output = this->get_result();
|
||||
output.allocate_texture(domain);
|
||||
output.bind_as_image(shader, "domain_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, output.domain().data_size);
|
||||
|
||||
input.unbind_as_texture();
|
||||
output.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
const char *RealizeOnDomainOperation::get_realization_shader_name()
|
||||
{
|
||||
const Interpolation interpolation = get_input().get_realization_options().interpolation;
|
||||
if (interpolation == Interpolation::Bicubic) {
|
||||
switch (this->get_input().type()) {
|
||||
case ResultType::Float:
|
||||
return "compositor_realize_on_domain_bicubic_float";
|
||||
case ResultType::Float2:
|
||||
return "compositor_realize_on_domain_bicubic_float2";
|
||||
case ResultType::Float3:
|
||||
/* Float3 is internally stored in a float4 texture due to GPU module limitations. */
|
||||
return "compositor_realize_on_domain_bicubic_float4";
|
||||
case ResultType::Float4:
|
||||
return "compositor_realize_on_domain_bicubic_float4";
|
||||
case ResultType::Color:
|
||||
return "compositor_realize_on_domain_bicubic_float4";
|
||||
case ResultType::Int:
|
||||
return "compositor_realize_on_domain_int";
|
||||
case ResultType::Int2:
|
||||
return "compositor_realize_on_domain_int2";
|
||||
case ResultType::Int3:
|
||||
/* Int3 is internally stored in a int4 texture due to GPU module limitations. */
|
||||
return "compositor_realize_on_domain_int4";
|
||||
case ResultType::Int4:
|
||||
return "compositor_realize_on_domain_int4";
|
||||
case ResultType::Bool:
|
||||
return "compositor_realize_on_domain_bool";
|
||||
case ResultType::Float4x4:
|
||||
return "compositor_realize_on_domain_float4x4";
|
||||
case ResultType::Menu:
|
||||
return "compositor_realize_on_domain_menu";
|
||||
case ResultType::Quaternion:
|
||||
return "compositor_realize_on_domain_bicubic_float4";
|
||||
case ResultType::String:
|
||||
case ResultType::Object:
|
||||
case ResultType::Image:
|
||||
case ResultType::Font:
|
||||
case ResultType::Scene:
|
||||
case ResultType::Text:
|
||||
case ResultType::Mask:
|
||||
/* Single only types do not support GPU code path. */
|
||||
BLI_assert(Result::is_single_value_only_type(this->get_input().type()));
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (this->get_input().type()) {
|
||||
case ResultType::Float:
|
||||
return "compositor_realize_on_domain_float";
|
||||
case ResultType::Float2:
|
||||
return "compositor_realize_on_domain_float2";
|
||||
case ResultType::Float3:
|
||||
/* Float3 is internally stored in a float4 texture due to GPU module limitations. */
|
||||
return "compositor_realize_on_domain_float4";
|
||||
case ResultType::Float4:
|
||||
case ResultType::Color:
|
||||
return (interpolation == Interpolation::Anisotropic) ?
|
||||
"compositor_realize_on_domain_anisotropic_float4" :
|
||||
"compositor_realize_on_domain_float4";
|
||||
case ResultType::Int:
|
||||
return "compositor_realize_on_domain_int";
|
||||
case ResultType::Int2:
|
||||
return "compositor_realize_on_domain_int2";
|
||||
case ResultType::Int3:
|
||||
/* Int3 is internally stored in a int4 texture due to GPU module limitations. */
|
||||
return "compositor_realize_on_domain_int4";
|
||||
case ResultType::Int4:
|
||||
return "compositor_realize_on_domain_int4";
|
||||
case ResultType::Bool:
|
||||
return "compositor_realize_on_domain_bool";
|
||||
case ResultType::Float4x4:
|
||||
return "compositor_realize_on_domain_float4x4";
|
||||
case ResultType::Menu:
|
||||
return "compositor_realize_on_domain_menu";
|
||||
case ResultType::Quaternion:
|
||||
return "compositor_realize_on_domain_float4";
|
||||
case ResultType::String:
|
||||
case ResultType::Object:
|
||||
case ResultType::Image:
|
||||
case ResultType::Font:
|
||||
case ResultType::Scene:
|
||||
case ResultType::Text:
|
||||
case ResultType::Mask:
|
||||
/* Single only types do not support GPU code path. */
|
||||
BLI_assert(Result::is_single_value_only_type(this->get_input().type()));
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void realize_on_domain(const Result &input, Result &output, const float3x3 &transformation)
|
||||
{
|
||||
const RealizationOptions realization_options = input.get_realization_options();
|
||||
const float2x2 jacobian(transformation);
|
||||
parallel_for(output.domain().data_size, [&](const int2 texel) {
|
||||
const float2 coordinates = math::transform_point(transformation, float2(texel));
|
||||
T sample = input.sample<T>(coordinates,
|
||||
realization_options.interpolation,
|
||||
realization_options.extension_x,
|
||||
realization_options.extension_y,
|
||||
jacobian);
|
||||
output.store_pixel(texel, sample);
|
||||
});
|
||||
}
|
||||
|
||||
void RealizeOnDomainOperation::realize_on_domain_cpu(const float3x3 &transformation)
|
||||
{
|
||||
Result &input = this->get_input();
|
||||
Result &output = this->get_result();
|
||||
|
||||
const Domain domain = this->compute_domain();
|
||||
output.allocate_texture(domain);
|
||||
|
||||
input.get_cpp_type()
|
||||
.to_static_type<float,
|
||||
float2,
|
||||
float3,
|
||||
float4,
|
||||
Color,
|
||||
int32_t,
|
||||
int2,
|
||||
int3,
|
||||
int4,
|
||||
bool,
|
||||
float4x4,
|
||||
nodes::MenuValue,
|
||||
math::Quaternion>(
|
||||
[&]<typename T>() { realize_on_domain<T>(input, output, transformation); });
|
||||
}
|
||||
|
||||
Domain RealizeOnDomainOperation::compute_domain()
|
||||
{
|
||||
return target_domain_;
|
||||
}
|
||||
|
||||
SimpleOperation *RealizeOnDomainOperation::construct_if_needed(
|
||||
Context &context,
|
||||
const Result &input_result,
|
||||
const InputDescriptor &input_descriptor,
|
||||
const Domain &operation_domain)
|
||||
{
|
||||
/* This input doesn't need realization, the operation is not needed. */
|
||||
if (input_descriptor.realization_mode == InputRealizationMode::None) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* The input expects a single value and if no single value is provided, it will be ignored and a
|
||||
* default value will be used, so no need to realize it and the operation is not needed. */
|
||||
if (input_descriptor.expects_single_value) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Input result is a single value and does not need realization, the operation is not needed. */
|
||||
if (input_result.is_single_value()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* If we are realizing on the operation domain, then our target domain is the operation domain,
|
||||
* otherwise, we are only realizing the transforms, then our target domain is the input's one. */
|
||||
const bool use_operation_domain = input_descriptor.realization_mode ==
|
||||
InputRealizationMode::OperationDomain;
|
||||
const Domain target_domain = use_operation_domain ? operation_domain : input_result.domain();
|
||||
|
||||
const bool should_realize_translation = input_descriptor.realization_mode ==
|
||||
InputRealizationMode::Transforms;
|
||||
const Domain realized_target_domain = target_domain.realize_transformation(
|
||||
should_realize_translation);
|
||||
|
||||
/* The input have an almost identical domain to the realized target domain, so no need to realize
|
||||
* it and the operation is not needed. */
|
||||
if (Domain::is_equal(input_result.domain(), realized_target_domain)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new RealizeOnDomainOperation(context, realized_target_domain, input_descriptor.type);
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
206
blender-5.2.0/source/blender/compositor/intern/render_context.cc
Normal file
206
blender-5.2.0/source/blender/compositor/intern/render_context.cc
Normal file
@@ -0,0 +1,206 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utf8.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
#include "IMB_imbuf_types.hh"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_windowmanager_types.h"
|
||||
|
||||
#include "BKE_image.hh"
|
||||
#include "BKE_image_save.hh"
|
||||
#include "BKE_report.hh"
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "RE_pipeline.h"
|
||||
|
||||
#include "COM_render_context.hh"
|
||||
#include "COM_result.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------
|
||||
* File Output
|
||||
*/
|
||||
|
||||
FileOutput::FileOutput(const std::string &path,
|
||||
const ImageFormatData &format,
|
||||
int2 size,
|
||||
bool save_as_render)
|
||||
: path_(path), format_(format), save_as_render_(save_as_render)
|
||||
{
|
||||
render_result_ = MEM_new<RenderResult>("Temporary Render Result For File Output");
|
||||
|
||||
render_result_->rectx = size.x;
|
||||
render_result_->recty = size.y;
|
||||
|
||||
/* NOTE: set dummy values which will won't be used unless overwritten.
|
||||
* When `save_as_render` is set, this is overwritten by the scenes PPM setting.
|
||||
* We *could* support setting the DPI in the file output node too. */
|
||||
render_result_->ppm[0] = 0.0;
|
||||
render_result_->ppm[1] = 0.0;
|
||||
|
||||
/* File outputs are always single layer, as images are actually stored in passes on that single
|
||||
* layer. Create a single unnamed layer to add the passes to. A single unnamed layer is treated
|
||||
* by the EXR writer as a special case where the channel names take the form:
|
||||
* <pass-name>.<view-name>.<channel-id>
|
||||
* Otherwise, the layer name would have preceded in the pass name in yet another section. */
|
||||
RenderLayer *render_layer = MEM_new<RenderLayer>("Render Layer For File Output.");
|
||||
BLI_addtail(&render_result_->layers, render_layer);
|
||||
render_layer->name[0] = '\0';
|
||||
|
||||
/* File outputs do not support previews. */
|
||||
format_.flag &= ~R_IMF_FLAG_PREVIEW_JPG;
|
||||
}
|
||||
|
||||
FileOutput::~FileOutput()
|
||||
{
|
||||
RE_FreeRenderResult(render_result_);
|
||||
}
|
||||
|
||||
void FileOutput::add_view(const char *view_name)
|
||||
{
|
||||
/* Empty views can only be added for EXR images. */
|
||||
BLI_assert(ELEM(format_.imtype, R_IMF_IMTYPE_OPENEXR, R_IMF_IMTYPE_MULTILAYER));
|
||||
|
||||
RenderView *render_view = MEM_new<RenderView>("Render View For File Output.");
|
||||
BLI_addtail(&render_result_->views, render_view);
|
||||
STRNCPY_UTF8(render_view->name, view_name);
|
||||
}
|
||||
|
||||
void FileOutput::add_view(const char *view_name, const Result &data)
|
||||
{
|
||||
RenderView *render_view = MEM_new<RenderView>("Render View For File Output.");
|
||||
BLI_addtail(&render_result_->views, render_view);
|
||||
STRNCPY_UTF8(render_view->name, view_name);
|
||||
|
||||
ImColorMode color_mode = ImColorMode::RGBA;
|
||||
if (data.channels_count() == 1) {
|
||||
color_mode = ImColorMode::BW;
|
||||
}
|
||||
else if (data.channels_count() == 3) {
|
||||
color_mode = ImColorMode::RGB;
|
||||
}
|
||||
render_view->ibuf = IMB_allocImBuf(UNPACK2(data.domain().data_size), ImBufFlags::Zero);
|
||||
render_view->ibuf->color_mode = color_mode;
|
||||
if (data.sharing_info()) {
|
||||
render_view->ibuf->channels = data.channels_count();
|
||||
render_view->ibuf->float_buffer = ImBufFloatBuffer{
|
||||
.data = static_cast<const float *>(data.cpu_data().data()),
|
||||
.sharing_info = data.sharing_info(),
|
||||
.colorspace = nullptr};
|
||||
}
|
||||
else if (data.cpu_data().data() != render_view->ibuf->float_data()) {
|
||||
IMB_alloc_float_pixels(render_view->ibuf, data.channels_count(), false);
|
||||
std::memcpy(
|
||||
render_view->ibuf->float_data_for_write(), data.cpu_data().data(), data.size_in_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
void FileOutput::add_pass(const char *pass_name,
|
||||
const char *view_name,
|
||||
const char *channels,
|
||||
const Result &data)
|
||||
{
|
||||
/* Passes can only be added for EXR images. */
|
||||
BLI_assert(ELEM(format_.imtype, R_IMF_IMTYPE_OPENEXR, R_IMF_IMTYPE_MULTILAYER));
|
||||
|
||||
RenderLayer *render_layer = static_cast<RenderLayer *>(render_result_->layers.first);
|
||||
RenderPass *render_pass = MEM_new<RenderPass>("Render Pass For File Output.");
|
||||
BLI_addtail(&render_layer->passes, render_pass);
|
||||
STRNCPY(render_pass->name, pass_name);
|
||||
STRNCPY(render_pass->view, view_name);
|
||||
STRNCPY(render_pass->chan_id, channels);
|
||||
|
||||
render_pass->rectx = data.domain().data_size.x;
|
||||
render_pass->recty = data.domain().data_size.y;
|
||||
render_pass->channels = data.channels_count();
|
||||
|
||||
ImColorMode color_mode = ImColorMode::RGBA;
|
||||
if (render_pass->channels == 1) {
|
||||
color_mode = ImColorMode::BW;
|
||||
}
|
||||
else if (render_pass->channels == 3) {
|
||||
color_mode = ImColorMode::RGB;
|
||||
}
|
||||
render_pass->ibuf = IMB_allocImBuf(UNPACK2(data.domain().data_size), ImBufFlags::Zero);
|
||||
render_pass->ibuf->color_mode = color_mode;
|
||||
if (data.sharing_info()) {
|
||||
render_pass->ibuf->channels = data.channels_count();
|
||||
render_pass->ibuf->float_buffer = ImBufFloatBuffer{
|
||||
.data = static_cast<const float *>(data.cpu_data().data()),
|
||||
.sharing_info = data.sharing_info(),
|
||||
.colorspace = nullptr};
|
||||
}
|
||||
else if (data.cpu_data().data() != render_pass->ibuf->float_data()) {
|
||||
IMB_alloc_float_pixels(render_pass->ibuf, data.channels_count(), false);
|
||||
std::memcpy(
|
||||
render_pass->ibuf->float_data_for_write(), data.cpu_data().data(), data.size_in_bytes());
|
||||
}
|
||||
copy_v2_v2_db(render_pass->ibuf->ppm, render_result_->ppm);
|
||||
}
|
||||
|
||||
void FileOutput::add_meta_data(std::string key, std::string value)
|
||||
{
|
||||
meta_data_.add(key, value);
|
||||
}
|
||||
|
||||
void FileOutput::save(Scene *scene)
|
||||
{
|
||||
ReportList reports;
|
||||
BKE_reports_init(&reports, RPT_STORE);
|
||||
|
||||
/* Add scene stamp data as meta data as well as the custom meta data. */
|
||||
BKE_render_result_stamp_info(scene, nullptr, render_result_, false);
|
||||
for (const auto &field : meta_data_.items()) {
|
||||
BKE_render_result_stamp_data(render_result_, field.key.c_str(), field.value.c_str());
|
||||
}
|
||||
|
||||
/* NOTE: without this the file will be written without any density information.
|
||||
* So always write this. */
|
||||
if (save_as_render_ || true) {
|
||||
BKE_scene_ppm_get(&scene->r, render_result_->ppm);
|
||||
}
|
||||
|
||||
BKE_image_render_write(
|
||||
&reports, render_result_, scene, true, path_.c_str(), &format_, save_as_render_);
|
||||
|
||||
BKE_reports_free(&reports);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------
|
||||
* Render Context
|
||||
*/
|
||||
|
||||
FileOutput &RenderContext::get_file_output(std::string path,
|
||||
ImageFormatData format,
|
||||
int2 size,
|
||||
bool save_as_render)
|
||||
{
|
||||
return *file_outputs_.lookup_or_add_cb(
|
||||
path, [&]() { return std::make_unique<FileOutput>(path, format, size, save_as_render); });
|
||||
}
|
||||
|
||||
void RenderContext::save_file_outputs(Scene *scene)
|
||||
{
|
||||
for (std::unique_ptr<FileOutput> &file_output : file_outputs_.values()) {
|
||||
file_output->save(scene);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
1182
blender-5.2.0/source/blender/compositor/intern/result.cc
Normal file
1182
blender-5.2.0/source/blender/compositor/intern/result.cc
Normal file
File diff suppressed because it is too large
Load Diff
615
blender-5.2.0/source/blender/compositor/intern/scheduler.cc
Normal file
615
blender-5.2.0/source/blender/compositor/intern/scheduler.cc
Normal file
@@ -0,0 +1,615 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "BLI_index_range.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_stack.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "NOD_geo_index_switch.hh"
|
||||
#include "NOD_geo_menu_switch.hh"
|
||||
|
||||
#include "COM_context.hh"
|
||||
#include "COM_scheduler.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
/* Checks if the node group has a File Output node in it or in one of its descendants. */
|
||||
static bool has_file_output_recursive(const bNodeTree &node_group)
|
||||
{
|
||||
node_group.ensure_topology_cache();
|
||||
for (const bNode *node : node_group.nodes_by_type("CompositorNodeOutputFile"_ustr)) {
|
||||
if (!node->is_muted()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const bNode *group_node : node_group.group_nodes()) {
|
||||
if (!group_node->is_muted() && group_node->id) {
|
||||
if (has_file_output_recursive(*reinterpret_cast<const bNodeTree *>(group_node->id))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Checks if the node group with the given instance key has a Viewer node in it or in one of its
|
||||
* descendants. Only nodes of node groups whose instance key match that of the given active node
|
||||
* group instance key are considered active. */
|
||||
static bool has_viewer_recursive(const bNodeTree &node_group,
|
||||
const bNodeInstanceKey instance_key,
|
||||
const bNodeInstanceKey active_node_group_instance_key)
|
||||
{
|
||||
node_group.ensure_topology_cache();
|
||||
|
||||
/* If this is the active node group, check if a viewer node exists. */
|
||||
if (active_node_group_instance_key == instance_key) {
|
||||
for (const bNode *node : node_group.nodes_by_type("CompositorNodeViewer"_ustr)) {
|
||||
if (node->flag & NODE_DO_OUTPUT && !node->is_muted()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Otherwise, we have to check node groups recursively. */
|
||||
for (const bNode *group_node : node_group.group_nodes()) {
|
||||
if (group_node->is_muted() || !group_node->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bNodeTree &child_node_group = *reinterpret_cast<const bNodeTree *>(group_node->id);
|
||||
const bNodeInstanceKey child_instance_key = bke::node_instance_key(
|
||||
instance_key, &node_group, group_node);
|
||||
if (has_viewer_recursive(child_node_group, child_instance_key, active_node_group_instance_key))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Add the output nodes whose result should be computed to the given stack. This includes File
|
||||
* Output, Group Output, and Viewer nodes. This might also include group nodes that contain File
|
||||
* Output or Viewer nodes. */
|
||||
static void add_output_nodes(const Context &context,
|
||||
const bNodeTree &node_group,
|
||||
NodeGroupOperation &node_group_operation,
|
||||
NodeGroupOutputTypes needed_outputs_types,
|
||||
const bNodeInstanceKey instance_key,
|
||||
const bNodeInstanceKey active_node_group_instance_key,
|
||||
Stack<const bNode *> &node_stack)
|
||||
{
|
||||
node_group.ensure_topology_cache();
|
||||
|
||||
bool viewer_exists = false;
|
||||
/* Add group nodes that contain File Output and Viewer nodes. */
|
||||
for (const bNode *group_node : node_group.group_nodes()) {
|
||||
if (group_node->is_muted() || !group_node->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bNodeTree &child_tree = *reinterpret_cast<const bNodeTree *>(group_node->id);
|
||||
const bNodeInstanceKey child_instance_key = bke::node_instance_key(
|
||||
instance_key, &node_group, group_node);
|
||||
if (flag_is_set(needed_outputs_types, NodeGroupOutputTypes::ViewerNode) &&
|
||||
has_viewer_recursive(child_tree, child_instance_key, active_node_group_instance_key))
|
||||
{
|
||||
node_stack.push(group_node);
|
||||
viewer_exists = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (flag_is_set(needed_outputs_types, NodeGroupOutputTypes::FileOutputNode) &&
|
||||
has_file_output_recursive(child_tree))
|
||||
{
|
||||
node_stack.push(group_node);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add Warning nodes. */
|
||||
for (const bNode *node : node_group.nodes_by_type("GeometryNodeWarning"_ustr)) {
|
||||
if (!node->is_muted()) {
|
||||
node_stack.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add File Output nodes. */
|
||||
if (flag_is_set(needed_outputs_types, NodeGroupOutputTypes::FileOutputNode)) {
|
||||
for (const bNode *node : node_group.nodes_by_type("CompositorNodeOutputFile"_ustr)) {
|
||||
if (!node->is_muted()) {
|
||||
node_stack.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Add Viewer node. Only add the node if the node group is active or is a root node group and no
|
||||
* viewer node exists in descendants node groups. */
|
||||
const bool is_active_node_group = active_node_group_instance_key == instance_key;
|
||||
const bool is_root_node_group = instance_key == bke::NODE_INSTANCE_KEY_BASE;
|
||||
const bool should_add_viewer = is_active_node_group || (is_root_node_group && !viewer_exists);
|
||||
if (flag_is_set(needed_outputs_types, NodeGroupOutputTypes::ViewerNode) && should_add_viewer) {
|
||||
for (const bNode *node : node_group.nodes_by_type("CompositorNodeViewer"_ustr)) {
|
||||
if (node->flag & NODE_DO_OUTPUT && !node->is_muted()) {
|
||||
node_stack.push(node);
|
||||
viewer_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_any_group_output_needed = false;
|
||||
for (const bNodeTreeInterfaceSocket *output : node_group.interface_outputs()) {
|
||||
if (node_group_operation.get_result(output->identifier).should_compute()) {
|
||||
is_any_group_output_needed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* None of the node groups outputs are needed, so no need to add the Group Output node. */
|
||||
if (!is_any_group_output_needed) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Add Group Output node. None root node groups should always had a group output node. If the
|
||||
* context is treating viewer nodes as group outputs, then the group output should be ignored
|
||||
* even if needed. */
|
||||
const bool context_ignores_output = context.treat_viewer_as_group_output() && viewer_exists;
|
||||
if (!is_root_node_group ||
|
||||
(flag_is_set(needed_outputs_types, NodeGroupOutputTypes::GroupOutputNode) &&
|
||||
!context_ignores_output))
|
||||
{
|
||||
const bNode *output_node = node_group.group_output_node();
|
||||
if (output_node && !output_node->is_muted()) {
|
||||
node_stack.push(output_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns the value of input of the node with the given identifier in the given node group
|
||||
* operation. If the value can not be determined statically, a nullopt is returned. The value is
|
||||
* only known statically if the input is not connected or directly connected to a group input node
|
||||
* with the same socket type. */
|
||||
template<typename T, typename SocketT>
|
||||
static std::optional<T> get_input_socket_value(const bNode &node,
|
||||
const UString &identifier,
|
||||
NodeGroupOperation &node_group_operation)
|
||||
{
|
||||
const bNodeSocket &input = *node.input_by_identifier(identifier);
|
||||
if (!input.is_logically_linked()) {
|
||||
return T(input.default_value_typed<SocketT>()->value);
|
||||
}
|
||||
|
||||
const bNodeSocket *linked_output = input.logically_linked_sockets()[0];
|
||||
if (!linked_output->owner_node().is_group_input()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (linked_output->type != input.type) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return node_group_operation.get_input(linked_output->identifier).get_single_value_default<T>();
|
||||
}
|
||||
|
||||
/* Returns true if the given input of the given Switch node in the given node group operation is
|
||||
* needed by the node. */
|
||||
static bool is_switch_node_input_needed(const bNode &node,
|
||||
const bNodeSocket &input,
|
||||
NodeGroupOperation &node_group_operation)
|
||||
{
|
||||
const UString condition_identifier = "Switch"_ustr;
|
||||
if (input.identifier_ustr() == condition_identifier) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::optional<bool> condition = get_input_socket_value<bool, bNodeSocketValueBoolean>(
|
||||
node, condition_identifier, node_group_operation);
|
||||
if (!condition.has_value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (input.identifier_ustr() == "True"_ustr) == condition.value();
|
||||
}
|
||||
|
||||
/* returns true if the given input of the given menu switch node in the given node group operation
|
||||
* is needed by the node. */
|
||||
static bool is_menu_switch_node_input_needed(const bNode &node,
|
||||
const bNodeSocket &input,
|
||||
NodeGroupOperation &node_group_operation)
|
||||
{
|
||||
const UString menu_identifier = "Menu"_ustr;
|
||||
if (input.identifier_ustr() == menu_identifier) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::optional<nodes::MenuValue> menu =
|
||||
get_input_socket_value<nodes::MenuValue, bNodeSocketValueMenu>(
|
||||
node, menu_identifier, node_group_operation);
|
||||
if (!menu.has_value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const NodeEnumItem menu_item = NodeEnumItem{nullptr, nullptr, menu.value().value};
|
||||
const std::string identifier = nodes::MenuSwitchItemsAccessor::socket_identifier_for_item(
|
||||
menu_item);
|
||||
return input.identifier == identifier;
|
||||
}
|
||||
|
||||
/* Returns true if the given input of the given Index node in the given node group operation is
|
||||
* needed by the node. */
|
||||
static bool is_index_switch_node_input_needed(const bNode &node,
|
||||
const bNodeSocket &input,
|
||||
NodeGroupOperation &node_group_operation)
|
||||
{
|
||||
const UString index_identifier = "Index"_ustr;
|
||||
if (input.identifier_ustr() == index_identifier) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::optional<int> index = get_input_socket_value<int, bNodeSocketValueInt>(
|
||||
node, index_identifier, node_group_operation);
|
||||
if (!index.has_value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const NodeIndexSwitch &storage = *static_cast<const NodeIndexSwitch *>(node.storage);
|
||||
if (!IndexRange(storage.items_num).contains(index.value())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string identifier = nodes::IndexSwitchItemsAccessor::socket_identifier_for_item(
|
||||
storage.items[index.value()]);
|
||||
return input.identifier == identifier;
|
||||
}
|
||||
|
||||
/* Returns true if the given input of the given node in the given node group operation is needed by
|
||||
* the compositor. */
|
||||
static bool is_input_needed(const bNode &node,
|
||||
const bNodeSocket &input,
|
||||
NodeGroupOperation &node_group_operation)
|
||||
{
|
||||
if (node.is_group_output()) {
|
||||
return node_group_operation.get_result(input.identifier).should_compute();
|
||||
}
|
||||
|
||||
if (node.is_type("GeometryNodeSwitch"_ustr)) {
|
||||
return is_switch_node_input_needed(node, input, node_group_operation);
|
||||
}
|
||||
|
||||
if (node.is_type("GeometryNodeMenuSwitch"_ustr)) {
|
||||
return is_menu_switch_node_input_needed(node, input, node_group_operation);
|
||||
}
|
||||
|
||||
if (node.is_type("GeometryNodeIndexSwitch"_ustr)) {
|
||||
return is_index_switch_node_input_needed(node, input, node_group_operation);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* A type representing a mapping that associates each node with a heuristic estimation of the
|
||||
* number of intermediate buffers needed to compute it and all of its dependencies. See the
|
||||
* compute_number_of_needed_buffers function for more information. */
|
||||
using NeededBuffers = Map<const bNode *, int>;
|
||||
|
||||
/* Compute a heuristic estimation of the number of intermediate buffers needed to compute each node
|
||||
* and all of its dependencies for all nodes that the given node depends on. The output is a map
|
||||
* that maps each node with the number of intermediate buffers needed to compute it and all of its
|
||||
* dependencies.
|
||||
*
|
||||
* Consider a node that takes n number of buffers as an input from a number of node dependencies,
|
||||
* which we shall call the input nodes. The node also computes and outputs m number of buffers.
|
||||
* In order for the node to compute its output, a number of intermediate buffers will be needed.
|
||||
* Since the node takes n buffers and outputs m buffers, then the number of buffers directly
|
||||
* needed by the node is (n + m). But each of the input buffers are computed by a node that, in
|
||||
* turn, needs a number of buffers to compute its output. So the total number of buffers needed
|
||||
* to compute the output of the node is max(n + m, d) where d is the number of buffers needed by
|
||||
* the input node that needs the largest number of buffers. We only consider the input node that
|
||||
* needs the largest number of buffers, because those buffers can be reused by any input node
|
||||
* that needs a lesser number of buffers.
|
||||
*
|
||||
* Pixel nodes, however, are a special case because links between two pixel nodes inside the same
|
||||
* pixel operation don't pass a buffer, but a single value in the pixel processor. So for pixel
|
||||
* nodes, only inputs and outputs linked to nodes that are not pixel nodes should be considered.
|
||||
* Note that this might not actually be true, because the compiler may decide to split a pixel
|
||||
* operation into multiples ones that will pass buffers, but this is not something that can be
|
||||
* known at scheduling-time. See the discussion in COM_compile_state.hh, COM_evaluator.hh, and
|
||||
* COM_shader_operation.hh for more information. In the node tree shown below, node 4 will have
|
||||
* exactly the same number of needed buffers by node 3, because its inputs and outputs are all
|
||||
* internally linked in the pixel operation.
|
||||
*
|
||||
* Pixel Operation
|
||||
* +------------------------------------------------------+
|
||||
* .------------. | .------------. .------------. .------------. | .------------.
|
||||
* | Node 1 | | | Node 3 | | Node 4 | | Node 5 | | | Node 6 |
|
||||
* | |----|--| |--| |------| |--|--| |
|
||||
* | | .-|--| | | | .---| | | | |
|
||||
* '------------' | | '------------' '------------' | '------------' | '------------'
|
||||
* | +----------------------------------|-------------------+
|
||||
* .------------. | |
|
||||
* | Node 2 | | |
|
||||
* | |--'------------------------------------'
|
||||
* | |
|
||||
* '------------'
|
||||
*
|
||||
* Note that the computed output is not guaranteed to be accurate, and will not be in most cases.
|
||||
* The computation is merely a heuristic estimation that works well in most cases. This is due to a
|
||||
* number of reasons:
|
||||
* - The node tree is actually a graph that allows output sharing, which is not something that was
|
||||
* taken into consideration in this implementation because it is difficult to correctly consider.
|
||||
* - Each node may allocate any number of internal buffers, which is not taken into account in this
|
||||
* implementation because it rarely affects the output and is done by very few nodes.
|
||||
* - The compiler may decide to compiler the schedule differently depending on runtime information
|
||||
* which we can merely speculate at scheduling-time as described above. */
|
||||
static NeededBuffers compute_number_of_needed_buffers(Stack<const bNode *> &output_nodes,
|
||||
NodeGroupOperation &node_group_operation)
|
||||
{
|
||||
NeededBuffers needed_buffers;
|
||||
|
||||
/* A stack of nodes used to traverse the node group starting from the output nodes. */
|
||||
Stack<const bNode *> node_stack = output_nodes;
|
||||
|
||||
/* Traverse the node group in a post order depth first manner and compute the number of needed
|
||||
* buffers for each node. Post order traversal guarantee that all the node dependencies of each
|
||||
* node are computed before it. This is done by pushing all the uncomputed node dependencies to
|
||||
* the node stack first and only popping and computing the node when all its node dependencies
|
||||
* were computed. */
|
||||
while (!node_stack.is_empty()) {
|
||||
/* Do not pop the node immediately, as it may turn out that we can't compute its number of
|
||||
* needed buffers just yet because its dependencies weren't computed, it will be popped later
|
||||
* when needed. */
|
||||
const bNode &node = *node_stack.peek();
|
||||
|
||||
/* Go over the node dependencies connected to the inputs of the node and push them to the node
|
||||
* stack if they were not computed already. */
|
||||
Set<const bNode *> pushed_nodes;
|
||||
for (const bNodeSocket *input : node.input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_input_needed(node, *input, node_group_operation)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Get the output linked to the input. If it is null, that means the input is unlinked and
|
||||
* has no dependency node. */
|
||||
const bNodeSocket *output = get_output_linked_to_input(*input);
|
||||
if (!output) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The node dependency was already computed or pushed before, so skip it. */
|
||||
if (needed_buffers.contains(&output->owner_node()) ||
|
||||
pushed_nodes.contains(&output->owner_node()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The output node needs to be computed, push the node dependency to the node stack and
|
||||
* indicate that it was pushed. */
|
||||
node_stack.push(&output->owner_node());
|
||||
pushed_nodes.add_new(&output->owner_node());
|
||||
}
|
||||
|
||||
/* If any of the node dependencies were pushed, that means that not all of them were computed
|
||||
* and consequently we can't compute the number of needed buffers for this node just yet. */
|
||||
if (!pushed_nodes.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We don't need to store the result of the pop because we already peeked at it before. */
|
||||
node_stack.pop();
|
||||
|
||||
/* Compute the number of buffers that the node takes as an input as well as the number of
|
||||
* buffers needed to compute the most demanding of the node dependencies. */
|
||||
int number_of_input_buffers = 0;
|
||||
int buffers_needed_by_dependencies = 0;
|
||||
for (const bNodeSocket *input : node.input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_input_needed(node, *input, node_group_operation)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Get the output linked to the input. If it is null, that means the input is unlinked.
|
||||
* Unlinked inputs do not take a buffer, so skip those inputs. */
|
||||
const bNodeSocket *output = get_output_linked_to_input(*input);
|
||||
if (!output) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Since this input is linked, if the link is not between two pixel nodes, it means that the
|
||||
* node takes a buffer through this input and so we increment the number of input buffers. */
|
||||
if (!is_pixel_node(node) || !is_pixel_node(output->owner_node())) {
|
||||
number_of_input_buffers++;
|
||||
}
|
||||
|
||||
/* If the number of buffers needed by the node dependency is more than the total number of
|
||||
* buffers needed by the dependencies, then update the latter to be the former. This is
|
||||
* computing the "d" in the aforementioned equation "max(n + m, d)". */
|
||||
const int buffers_needed_by_dependency = needed_buffers.lookup(&output->owner_node());
|
||||
buffers_needed_by_dependencies = std::max(buffers_needed_by_dependency,
|
||||
buffers_needed_by_dependencies);
|
||||
}
|
||||
|
||||
/* Compute the number of buffers that will be computed/output by this node. */
|
||||
int number_of_output_buffers = 0;
|
||||
for (const bNodeSocket *output : node.output_sockets()) {
|
||||
if (!is_socket_available(output)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The output is not linked, it outputs no buffer. */
|
||||
if (!output->is_logically_linked()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If any of the links is not between two pixel nodes, it means that the node outputs
|
||||
* a buffer through this output and so we increment the number of output buffers. */
|
||||
if (!is_pixel_node(node) ||
|
||||
is_output_linked_to_input_conditioned(*output, [&](const bNodeSocket &input) {
|
||||
return !is_pixel_node(input.owner_node());
|
||||
}))
|
||||
{
|
||||
number_of_output_buffers++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Compute the heuristic estimation of the number of needed intermediate buffers to compute
|
||||
* this node and all of its dependencies. This is computing the aforementioned equation
|
||||
* "max(n + m, d)". */
|
||||
const int total_buffers = std::max(number_of_input_buffers + number_of_output_buffers,
|
||||
buffers_needed_by_dependencies);
|
||||
needed_buffers.add(&node, total_buffers);
|
||||
}
|
||||
|
||||
return needed_buffers;
|
||||
}
|
||||
|
||||
/* There are multiple different possible orders of evaluating a node graph, each of which needs
|
||||
* to allocate a number of intermediate buffers to store its intermediate results. It follows
|
||||
* that we need to find the evaluation order which uses the least amount of intermediate buffers.
|
||||
* For instance, consider a node that takes two input buffers A and B. Each of those buffers is
|
||||
* computed through a number of nodes constituting a sub-graph whose root is the node that
|
||||
* outputs that buffer. Suppose the number of intermediate buffers needed to compute A and B are
|
||||
* N(A) and N(B) respectively and N(A) > N(B). Then evaluating the sub-graph computing A would be
|
||||
* a better option than that of B, because had B was computed first, its outputs will need to be
|
||||
* stored in extra buffers in addition to the buffers needed by A. The number of buffers needed by
|
||||
* each node is estimated as described in the compute_number_of_needed_buffers function.
|
||||
*
|
||||
* This is a heuristic generalization of the Sethi-Ullman algorithm, a generalization that
|
||||
* doesn't always guarantee an optimal evaluation order, as the optimal evaluation order is very
|
||||
* difficult to compute, however, this method works well in most cases. Moreover it assumes that
|
||||
* all buffers will have roughly the same size, which may not always be the case. */
|
||||
Schedule compute_schedule(const Context &context,
|
||||
const bNodeTree &node_group,
|
||||
NodeGroupOperation &node_group_operation,
|
||||
NodeGroupOutputTypes needed_outputs_types,
|
||||
const bNodeInstanceKey instance_key,
|
||||
const bNodeInstanceKey active_node_group_instance_key)
|
||||
{
|
||||
Schedule schedule;
|
||||
|
||||
/* Validate node group. */
|
||||
node_group.ensure_topology_cache();
|
||||
if (node_group.has_available_link_cycle()) {
|
||||
context.set_info_message("Compositor node group has cyclic links.");
|
||||
return schedule;
|
||||
}
|
||||
|
||||
/* A stack of nodes used to traverse the node group starting from the output nodes. */
|
||||
Stack<const bNode *> node_stack;
|
||||
|
||||
/* Add the output nodes whose result should be computed to the stack. */
|
||||
add_output_nodes(context,
|
||||
node_group,
|
||||
node_group_operation,
|
||||
needed_outputs_types,
|
||||
instance_key,
|
||||
active_node_group_instance_key,
|
||||
node_stack);
|
||||
|
||||
/* No output nodes, the node group has no effect, return an empty schedule. */
|
||||
if (node_stack.is_empty()) {
|
||||
return schedule;
|
||||
}
|
||||
|
||||
/* Compute the number of buffers needed by each node connected to the outputs. */
|
||||
const NeededBuffers needed_buffers = compute_number_of_needed_buffers(node_stack,
|
||||
node_group_operation);
|
||||
|
||||
/* Traverse the node group in a post order depth first manner, scheduling the nodes in an order
|
||||
* informed by the number of buffers needed by each node. Post order traversal guarantee that all
|
||||
* the node dependencies of each node are scheduled before it. This is done by pushing all the
|
||||
* unscheduled node dependencies to the node stack first and only popping and scheduling the node
|
||||
* when all its node dependencies were scheduled. */
|
||||
while (!node_stack.is_empty()) {
|
||||
/* Do not pop the node immediately, as it may turn out that we can't schedule it just yet
|
||||
* because its dependencies weren't scheduled, it will be popped later when needed. */
|
||||
const bNode &node = *node_stack.peek();
|
||||
|
||||
/* Compute the nodes directly connected to the node inputs sorted by their needed buffers such
|
||||
* that the node with the lowest number of needed buffers comes first. Note that we actually
|
||||
* want the node with the highest number of needed buffers to be schedule first, but since
|
||||
* those are pushed to the traversal stack, we need to push them in reverse order. */
|
||||
Vector<const bNode *> sorted_dependency_nodes;
|
||||
for (const bNodeSocket *input : node.input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_input_needed(node, *input, node_group_operation)) {
|
||||
schedule.unneeded_inputs.add(input);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Get the output linked to the input. If it is null, that means the input is unlinked and
|
||||
* has no dependency node, so skip it. */
|
||||
const bNodeSocket *output = get_output_linked_to_input(*input);
|
||||
if (!output) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The dependency node was added before, so skip it. The number of dependency nodes is very
|
||||
* small, typically less than 3, so a linear search is okay. */
|
||||
if (sorted_dependency_nodes.contains(&output->owner_node())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The dependency node was already schedule, so skip it. */
|
||||
if (schedule.nodes.contains(&output->owner_node())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Sort in ascending order on insertion, the number of dependency nodes is very small,
|
||||
* typically less than 3, so insertion sort is okay. */
|
||||
int insertion_position = 0;
|
||||
for (int i = 0; i < sorted_dependency_nodes.size(); i++) {
|
||||
if (needed_buffers.lookup(&output->owner_node()) >
|
||||
needed_buffers.lookup(sorted_dependency_nodes[i]))
|
||||
{
|
||||
insertion_position++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
sorted_dependency_nodes.insert(insertion_position, &output->owner_node());
|
||||
}
|
||||
|
||||
/* Push the sorted dependency nodes to the node stack in order. */
|
||||
for (const bNode *dependency_node : sorted_dependency_nodes) {
|
||||
node_stack.push(dependency_node);
|
||||
}
|
||||
|
||||
/* If there are no sorted dependency nodes, that means they were all already scheduled or that
|
||||
* none exists in the first place, so we can pop and schedule the node now. */
|
||||
if (sorted_dependency_nodes.is_empty()) {
|
||||
/* The node might have already been scheduled, so we don't use add_new here and simply don't
|
||||
* add it if it was already scheduled. */
|
||||
schedule.nodes.add(node_stack.pop());
|
||||
}
|
||||
}
|
||||
|
||||
return schedule;
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
144
blender-5.2.0/source/blender/compositor/intern/shader_node.cc
Normal file
144
blender-5.2.0/source/blender/compositor/intern/shader_node.cc
Normal file
@@ -0,0 +1,144 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "COM_shader_node.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
ShaderNode::ShaderNode(const bNode &node) : node_(node)
|
||||
{
|
||||
this->populate_inputs();
|
||||
this->populate_outputs();
|
||||
}
|
||||
|
||||
void ShaderNode::compile(GPUMaterial *material)
|
||||
{
|
||||
node_.typeinfo->gpu_fn(
|
||||
material, const_cast<bNode *>(&node_), nullptr, inputs_.data(), outputs_.data());
|
||||
}
|
||||
|
||||
GPUNodeStack &ShaderNode::get_input(const StringRef identifier)
|
||||
{
|
||||
return GPU_node_get_input(node_, inputs_.data(), identifier);
|
||||
}
|
||||
|
||||
GPUNodeStack &ShaderNode::get_output(const StringRef identifier)
|
||||
{
|
||||
return GPU_node_get_output(node_, outputs_.data(), identifier);
|
||||
}
|
||||
|
||||
static GPUType gpu_type_from_socket(const bNodeSocket &socket)
|
||||
{
|
||||
switch (socket.type) {
|
||||
case SOCK_FLOAT:
|
||||
return GPU_FLOAT;
|
||||
case SOCK_INT:
|
||||
/* GPUMaterial doesn't support int, so it is passed as a float. */
|
||||
return GPU_FLOAT;
|
||||
case SOCK_BOOLEAN:
|
||||
/* GPUMaterial doesn't support boolean, so it is passed as a float. */
|
||||
return GPU_FLOAT;
|
||||
case SOCK_VECTOR:
|
||||
switch (socket.default_value_typed<bNodeSocketValueVector>()->dimensions) {
|
||||
case 2:
|
||||
return GPU_VEC2;
|
||||
case 3:
|
||||
return GPU_VEC3;
|
||||
case 4:
|
||||
return GPU_VEC4;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return GPU_NONE;
|
||||
}
|
||||
case SOCK_INT_VECTOR:
|
||||
/* GPUMaterial doesn't support int[23], so it is passed as a float[23]. */
|
||||
switch (socket.default_value_typed<bNodeSocketValueIntVector>()->dimensions) {
|
||||
case 2:
|
||||
return GPU_VEC2;
|
||||
case 3:
|
||||
return GPU_VEC3;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return GPU_NONE;
|
||||
}
|
||||
case SOCK_RGBA:
|
||||
case SOCK_ROTATION:
|
||||
return GPU_VEC4;
|
||||
case SOCK_MATRIX:
|
||||
return GPU_MAT4;
|
||||
case SOCK_MENU:
|
||||
/* GPUMaterial doesn't support int, so it is passed as a float. */
|
||||
return GPU_FLOAT;
|
||||
case SOCK_STRING:
|
||||
case SOCK_OBJECT:
|
||||
case SOCK_IMAGE:
|
||||
case SOCK_FONT:
|
||||
case SOCK_SCENE:
|
||||
case SOCK_TEXT_ID:
|
||||
case SOCK_MASK:
|
||||
/* Single only types do not support GPU code path. */
|
||||
BLI_assert(Result::is_single_value_only_type(get_node_socket_result_type(&socket)));
|
||||
BLI_assert_unreachable();
|
||||
return GPU_NONE;
|
||||
default:
|
||||
/* The GPU material compiler will skip unsupported sockets if GPU_NONE is provided. So this
|
||||
* is an appropriate and a valid type for unsupported sockets. */
|
||||
return GPU_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
static void populate_gpu_node_stack(const bNodeSocket &socket, GPUNodeStack &stack)
|
||||
{
|
||||
/* Make sure this stack is not marked as the end of the stack array. */
|
||||
stack.end = false;
|
||||
/* This will be initialized later by the GPU material compiler or the compile method. */
|
||||
stack.link = nullptr;
|
||||
/* This will be initialized by the GPU material compiler if needed. */
|
||||
zero_v4(stack.vec);
|
||||
|
||||
stack.sockettype = socket.type;
|
||||
stack.type = gpu_type_from_socket(socket);
|
||||
|
||||
stack.hasinput = socket.is_logically_linked();
|
||||
stack.hasoutput = socket.is_logically_linked();
|
||||
}
|
||||
|
||||
void ShaderNode::populate_inputs()
|
||||
{
|
||||
/* Reserve a stack for each input in addition to an extra stack at the end to mark the end of the
|
||||
* array, as this is what the GPU module functions expect. */
|
||||
const int num_input_sockets = node_.input_sockets().size();
|
||||
inputs_.resize(num_input_sockets + 1);
|
||||
inputs_.last().end = true;
|
||||
|
||||
for (int i = 0; i < num_input_sockets; i++) {
|
||||
populate_gpu_node_stack(node_.input_socket(i), inputs_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderNode::populate_outputs()
|
||||
{
|
||||
/* Reserve a stack for each output in addition to an extra stack at the end to mark the end of
|
||||
* the array, as this is what the GPU module functions expect. */
|
||||
const int num_output_sockets = node_.output_sockets().size();
|
||||
outputs_.resize(num_output_sockets + 1);
|
||||
outputs_.last().end = true;
|
||||
|
||||
for (int i = 0; i < num_output_sockets; i++) {
|
||||
populate_gpu_node_stack(node_.output_socket(i), outputs_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
1132
blender-5.2.0/source/blender/compositor/intern/shader_operation.cc
Normal file
1132
blender-5.2.0/source/blender/compositor/intern/shader_operation.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "COM_input_descriptor.hh"
|
||||
#include "COM_operation.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_simple_operation.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
const StringRef SimpleOperation::input_identifier_ = StringRef("Input");
|
||||
const StringRef SimpleOperation::output_identifier_ = StringRef("Output");
|
||||
|
||||
Result &SimpleOperation::get_result()
|
||||
{
|
||||
return Operation::get_result(output_identifier_);
|
||||
}
|
||||
|
||||
void SimpleOperation::map_input_to_result(Result *result)
|
||||
{
|
||||
Operation::map_input_to_result(input_identifier_, result);
|
||||
}
|
||||
|
||||
void SimpleOperation::evaluate_input_processors() {}
|
||||
|
||||
Result &SimpleOperation::get_input()
|
||||
{
|
||||
return Operation::get_input(input_identifier_);
|
||||
}
|
||||
|
||||
void SimpleOperation::populate_result(const ResultType type)
|
||||
{
|
||||
Operation::populate_result(output_identifier_, type);
|
||||
|
||||
/* The result of a simple operation is guaranteed to have a single user. */
|
||||
get_result().set_reference_count(1);
|
||||
}
|
||||
|
||||
void SimpleOperation::declare_input_descriptor(InputDescriptor descriptor)
|
||||
{
|
||||
Operation::declare_input_descriptor(input_identifier_, descriptor);
|
||||
}
|
||||
|
||||
InputDescriptor &SimpleOperation::get_input_descriptor()
|
||||
{
|
||||
return Operation::get_input_descriptor(input_identifier_);
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
@@ -0,0 +1,167 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_math_euler.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "COM_operation.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_single_value_node_input_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
const StringRef SingleValueNodeInputOperation::output_identifier_ = StringRef("Output");
|
||||
|
||||
SingleValueNodeInputOperation::SingleValueNodeInputOperation(Context &context,
|
||||
const bNodeSocket &input_socket)
|
||||
: Operation(context), input_socket_(input_socket)
|
||||
{
|
||||
this->populate_result(get_node_socket_result_type(&input_socket));
|
||||
}
|
||||
|
||||
void SingleValueNodeInputOperation::execute()
|
||||
{
|
||||
Result &result = this->get_result();
|
||||
result.allocate_single_value();
|
||||
|
||||
switch (input_socket_.type) {
|
||||
case SOCK_FLOAT: {
|
||||
const float value = input_socket_.default_value_typed<bNodeSocketValueFloat>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_INT: {
|
||||
const int value = input_socket_.default_value_typed<bNodeSocketValueInt>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_BOOLEAN: {
|
||||
const bool value = input_socket_.default_value_typed<bNodeSocketValueBoolean>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_VECTOR: {
|
||||
switch (input_socket_.default_value_typed<bNodeSocketValueVector>()->dimensions) {
|
||||
case 2: {
|
||||
const float2 value = input_socket_.default_value_typed<bNodeSocketValueVector>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
const float3 value = input_socket_.default_value_typed<bNodeSocketValueVector>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
const float4 value = input_socket_.default_value_typed<bNodeSocketValueVector>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_INT_VECTOR: {
|
||||
switch (input_socket_.default_value_typed<bNodeSocketValueIntVector>()->dimensions) {
|
||||
case 2: {
|
||||
const int2 value = input_socket_.default_value_typed<bNodeSocketValueIntVector>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
const int3 value = input_socket_.default_value_typed<bNodeSocketValueIntVector>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_RGBA: {
|
||||
const Color value = input_socket_.default_value_typed<bNodeSocketValueRGBA>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_MATRIX: {
|
||||
result.set_single_value(float4x4::identity());
|
||||
break;
|
||||
}
|
||||
case SOCK_MENU: {
|
||||
const int32_t value = input_socket_.default_value_typed<bNodeSocketValueMenu>()->value;
|
||||
result.set_single_value(nodes::MenuValue(value));
|
||||
break;
|
||||
}
|
||||
case SOCK_STRING: {
|
||||
const std::string value = input_socket_.default_value_typed<bNodeSocketValueString>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_ROTATION: {
|
||||
const bNodeSocketValueRotation *rotation =
|
||||
input_socket_.default_value_typed<bNodeSocketValueRotation>();
|
||||
const math::EulerXYZ euler(float3(rotation->value_euler));
|
||||
const math::Quaternion value = math::to_quaternion(euler);
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_OBJECT: {
|
||||
Object *value = input_socket_.default_value_typed<bNodeSocketValueObject>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_IMAGE: {
|
||||
Image *value = input_socket_.default_value_typed<bNodeSocketValueImage>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_FONT: {
|
||||
VFont *value = input_socket_.default_value_typed<bNodeSocketValueFont>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_SCENE: {
|
||||
Scene *value = input_socket_.default_value_typed<bNodeSocketValueScene>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_TEXT_ID: {
|
||||
Text *value = input_socket_.default_value_typed<bNodeSocketValueText>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_MASK: {
|
||||
Mask *value = input_socket_.default_value_typed<bNodeSocketValueMask>()->value;
|
||||
result.set_single_value(value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Result &SingleValueNodeInputOperation::get_result()
|
||||
{
|
||||
return Operation::get_result(output_identifier_);
|
||||
}
|
||||
|
||||
void SingleValueNodeInputOperation::populate_result(const ResultType type)
|
||||
{
|
||||
Operation::populate_result(output_identifier_, type);
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
@@ -0,0 +1,38 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "COM_static_cache_manager.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
void StaticCacheManager::reset()
|
||||
{
|
||||
symmetric_blur_weights.reset();
|
||||
symmetric_separable_blur_weights.reset();
|
||||
morphological_distance_feather_weights.reset();
|
||||
cached_masks.reset();
|
||||
smaa_precomputed_textures.reset();
|
||||
ocio_color_space_conversion_shaders.reset();
|
||||
ocio_to_display_shaders.reset();
|
||||
distortion_grids.reset();
|
||||
keying_screens.reset();
|
||||
cached_shaders.reset();
|
||||
bokeh_kernels.reset();
|
||||
cached_images.reset();
|
||||
deriche_gaussian_coefficients.reset();
|
||||
van_vliet_gaussian_coefficients.reset();
|
||||
fog_glow_kernels.reset();
|
||||
image_coordinates.reset();
|
||||
string_images.reset();
|
||||
}
|
||||
|
||||
void StaticCacheManager::free()
|
||||
{
|
||||
/* Resetting two times frees everything, because the first reset will declare everything as not
|
||||
* needed, and the second reset will delete all such resources. */
|
||||
this->reset();
|
||||
this->reset();
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
@@ -0,0 +1,33 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "COM_context.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_undefined_node_operation.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
/* A node operation that allocates all of its outputs as invalid. */
|
||||
class UndefinedNodeOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
this->allocate_default_remaining_outputs();
|
||||
}
|
||||
};
|
||||
|
||||
NodeOperation *get_undefined_node_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new UndefinedNodeOperation(context, node);
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
285
blender-5.2.0/source/blender/compositor/intern/utilities.cc
Normal file
285
blender-5.2.0/source/blender/compositor/intern/utilities.cc
Normal file
@@ -0,0 +1,285 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "NOD_node_declaration.hh"
|
||||
|
||||
#include "GPU_compute.hh"
|
||||
#include "GPU_shader.hh"
|
||||
|
||||
#include "COM_result.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
bool is_socket_available(const bNodeSocket *socket)
|
||||
{
|
||||
return socket->is_available() && StringRef(socket->idname) != "NodeSocketVirtual";
|
||||
}
|
||||
|
||||
const bNodeSocket *get_output_linked_to_input(const bNodeSocket &input)
|
||||
{
|
||||
if (!input.is_logically_linked()) {
|
||||
return nullptr;
|
||||
}
|
||||
return input.logically_linked_sockets()[0];
|
||||
}
|
||||
|
||||
ResultType socket_data_type_to_result_type(const eNodeSocketDatatype data_type,
|
||||
const std::optional<int> dimensions)
|
||||
{
|
||||
switch (data_type) {
|
||||
case SOCK_FLOAT:
|
||||
return ResultType::Float;
|
||||
case SOCK_INT:
|
||||
return ResultType::Int;
|
||||
case SOCK_BOOLEAN:
|
||||
return ResultType::Bool;
|
||||
case SOCK_VECTOR:
|
||||
switch (dimensions.value_or(3)) {
|
||||
case 2:
|
||||
return ResultType::Float2;
|
||||
case 3:
|
||||
return ResultType::Float3;
|
||||
case 4:
|
||||
return ResultType::Float4;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return ResultType::Float;
|
||||
}
|
||||
case SOCK_INT_VECTOR:
|
||||
switch (dimensions.value_or(2)) {
|
||||
case 2:
|
||||
return ResultType::Int2;
|
||||
case 3:
|
||||
return ResultType::Int3;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return ResultType::Float;
|
||||
}
|
||||
case SOCK_RGBA:
|
||||
return ResultType::Color;
|
||||
case SOCK_MATRIX:
|
||||
return ResultType::Float4x4;
|
||||
case SOCK_MENU:
|
||||
return ResultType::Menu;
|
||||
case SOCK_STRING:
|
||||
return ResultType::String;
|
||||
case SOCK_ROTATION:
|
||||
return ResultType::Quaternion;
|
||||
case SOCK_OBJECT:
|
||||
return ResultType::Object;
|
||||
case SOCK_IMAGE:
|
||||
return ResultType::Image;
|
||||
case SOCK_FONT:
|
||||
return ResultType::Font;
|
||||
case SOCK_SCENE:
|
||||
return ResultType::Scene;
|
||||
case SOCK_TEXT_ID:
|
||||
return ResultType::Text;
|
||||
case SOCK_MASK:
|
||||
return ResultType::Mask;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return ResultType::Float;
|
||||
}
|
||||
}
|
||||
|
||||
ResultType get_node_socket_result_type(const bNodeSocket *socket)
|
||||
{
|
||||
/* Gracefully handle undefined sockets, falling back to a float. */
|
||||
if (socket->typeinfo == &bke::NodeSocketTypeUndefined) {
|
||||
return ResultType::Float;
|
||||
}
|
||||
|
||||
const eNodeSocketDatatype socket_type = static_cast<eNodeSocketDatatype>(socket->type);
|
||||
if (socket_type == SOCK_VECTOR) {
|
||||
return socket_data_type_to_result_type(
|
||||
socket_type, socket->default_value_typed<bNodeSocketValueVector>()->dimensions);
|
||||
}
|
||||
if (socket_type == SOCK_INT_VECTOR) {
|
||||
return socket_data_type_to_result_type(
|
||||
socket_type, socket->default_value_typed<bNodeSocketValueIntVector>()->dimensions);
|
||||
}
|
||||
|
||||
return socket_data_type_to_result_type(socket_type);
|
||||
}
|
||||
|
||||
ResultType get_node_interface_socket_result_type(const bNodeTreeInterfaceSocket &socket)
|
||||
{
|
||||
/* Gracefully handle undefined interface sockets, falling back to a float. */
|
||||
if (socket.socket_typeinfo() == &bke::NodeSocketTypeUndefined) {
|
||||
return ResultType::Float;
|
||||
}
|
||||
|
||||
const eNodeSocketDatatype socket_type = socket.socket_typeinfo()->type;
|
||||
if (socket_type == SOCK_VECTOR) {
|
||||
return socket_data_type_to_result_type(
|
||||
socket_type, static_cast<bNodeSocketValueVector *>(socket.socket_data)->dimensions);
|
||||
}
|
||||
if (socket_type == SOCK_INT_VECTOR) {
|
||||
return socket_data_type_to_result_type(
|
||||
socket_type, static_cast<bNodeSocketValueIntVector *>(socket.socket_data)->dimensions);
|
||||
}
|
||||
|
||||
return socket_data_type_to_result_type(socket_type);
|
||||
}
|
||||
|
||||
bool is_output_linked_to_input_conditioned(const bNodeSocket &output,
|
||||
FunctionRef<bool(const bNodeSocket &)> condition)
|
||||
{
|
||||
for (const bNodeSocket *input : output.logically_linked_sockets()) {
|
||||
if (condition(*input)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int number_of_inputs_linked_to_output_conditioned(const bNodeSocket &output,
|
||||
FunctionRef<bool(const bNodeSocket &)> condition)
|
||||
{
|
||||
if (!output.is_logically_linked()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (const bNodeSocket *input : output.logically_linked_sockets()) {
|
||||
if (condition(*input)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
bool is_pixel_node(const bNode &node)
|
||||
{
|
||||
return node.typeinfo->build_multi_function;
|
||||
}
|
||||
|
||||
static std::optional<ImplicitInputType> get_implicit_input(
|
||||
const NodeDefaultInputType node_default_input_type)
|
||||
{
|
||||
switch (node_default_input_type) {
|
||||
case NodeDefaultInputType::NODE_DEFAULT_INPUT_VALUE:
|
||||
return std::nullopt;
|
||||
case NodeDefaultInputType::NODE_DEFAULT_INPUT_UNIFORM_IMAGE_COORDINATES:
|
||||
return ImplicitInputType::UniformImageCoordinates;
|
||||
case NodeDefaultInputType::NODE_DEFAULT_INPUT_SCENE_FRAME:
|
||||
return ImplicitInputType::SceneFrame;
|
||||
case NodeDefaultInputType::NODE_DEFAULT_INPUT_INDEX_FIELD:
|
||||
case NodeDefaultInputType::NODE_DEFAULT_INPUT_ID_INDEX_FIELD:
|
||||
case NodeDefaultInputType::NODE_DEFAULT_INPUT_NORMAL_FIELD:
|
||||
case NodeDefaultInputType::NODE_DEFAULT_INPUT_POSITION_FIELD:
|
||||
case NodeDefaultInputType::NODE_DEFAULT_INPUT_INSTANCE_TRANSFORM_FIELD:
|
||||
case NodeDefaultInputType::NODE_DEFAULT_INPUT_HANDLE_LEFT_FIELD:
|
||||
case NodeDefaultInputType::NODE_DEFAULT_INPUT_HANDLE_RIGHT_FIELD:
|
||||
case NodeDefaultInputType::NODE_DEFAULT_INPUT_SELF_OBJECT:
|
||||
break;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static int get_domain_priority(const bNodeSocket *input,
|
||||
const nodes::SocketDeclaration *socket_declaration)
|
||||
{
|
||||
/* Negative priority means no priority is set and we fall back to the index, that is, we
|
||||
* prioritize inputs according to their order. */
|
||||
if (socket_declaration->compositor_domain_priority() < 0) {
|
||||
return input->index();
|
||||
}
|
||||
return socket_declaration->compositor_domain_priority();
|
||||
}
|
||||
|
||||
InputDescriptor input_descriptor_from_input_socket(const bNodeSocket *socket)
|
||||
{
|
||||
InputDescriptor input_descriptor;
|
||||
input_descriptor.type = get_node_socket_result_type(socket);
|
||||
|
||||
/* Default to the index of the input as its domain priority in case the node does not have a
|
||||
* declaration. */
|
||||
input_descriptor.domain_priority = socket->index();
|
||||
|
||||
/* Not every node has a declaration, in which case we assume the default values for the rest of
|
||||
* the properties. */
|
||||
const nodes::NodeDeclaration *node_declaration = socket->owner_node().declaration();
|
||||
if (!node_declaration) {
|
||||
return input_descriptor;
|
||||
}
|
||||
const nodes::SocketDeclaration *socket_declaration = node_declaration->inputs[socket->index()];
|
||||
input_descriptor.domain_priority = get_domain_priority(socket, socket_declaration);
|
||||
input_descriptor.expects_single_value = socket_declaration->structure_type ==
|
||||
nodes::StructureType::Single;
|
||||
input_descriptor.realization_mode = static_cast<InputRealizationMode>(
|
||||
socket_declaration->compositor_realization_mode());
|
||||
input_descriptor.implicit_input = get_implicit_input(socket_declaration->default_input_type);
|
||||
|
||||
return input_descriptor;
|
||||
}
|
||||
|
||||
InputDescriptor input_descriptor_from_interface_input(const bNodeTree &node_group,
|
||||
const bNodeTreeInterfaceSocket &socket)
|
||||
{
|
||||
InputDescriptor input_descriptor;
|
||||
input_descriptor.type = get_node_interface_socket_result_type(socket);
|
||||
input_descriptor.domain_priority = node_group.interface_input_index(socket);
|
||||
input_descriptor.expects_single_value = socket.structure_type ==
|
||||
NodeSocketInterfaceStructureType::Single;
|
||||
input_descriptor.realization_mode = InputRealizationMode::None;
|
||||
input_descriptor.implicit_input = get_implicit_input(socket.default_input);
|
||||
|
||||
return input_descriptor;
|
||||
}
|
||||
|
||||
void compute_dispatch_threads_at_least(gpu::Shader *shader, int2 threads_range, int2 local_size)
|
||||
{
|
||||
/* If the threads range is divisible by the local size, dispatch the number of needed groups,
|
||||
* which is their division. If it is not divisible, then dispatch an extra group to cover the
|
||||
* remaining invocations, which means the actual threads range of the dispatch will be a bit
|
||||
* larger than the given one. */
|
||||
const int2 groups_to_dispatch = math::divide_ceil(threads_range, local_size);
|
||||
GPU_compute_dispatch(shader, groups_to_dispatch.x, groups_to_dispatch.y, 1);
|
||||
}
|
||||
|
||||
bool is_node_preview_needed(const bNode &node)
|
||||
{
|
||||
if (!(node.flag & NODE_PREVIEW)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node.flag & NODE_COLLAPSED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const bNodeSocket *find_preview_output_socket(const bNode &node)
|
||||
{
|
||||
if (!is_node_preview_needed(node)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const bNodeSocket *output : node.output_sockets()) {
|
||||
if (is_socket_available(output) && output->is_logically_linked()) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
Reference in New Issue
Block a user