Changeset View
Changeset View
Standalone View
Standalone View
source/blender/nodes/texture/node_texture_evaluate.cc
- This file was added.
| /* SPDX-License-Identifier: GPL-2.0-or-later | |||||
| * Copyright 2022 Blender Foundation. All rights reserved. */ | |||||
| /** \file | |||||
| * \ingroup nodes | |||||
| */ | |||||
| #include "BLI_threads.h" | |||||
| #include "BLI_vector.hh" | |||||
| #include "DNA_mesh_types.h" | |||||
| #include "DNA_meshdata_types.h" | |||||
| #include "DNA_node_types.h" | |||||
| #include "DNA_texture_types.h" | |||||
| #include "BKE_attribute.h" | |||||
| #include "BKE_type_conversions.hh" | |||||
| #include "NOD_derived_node_tree.hh" | |||||
| #include "NOD_geometry_exec.hh" | |||||
| #include "NOD_multi_function.hh" | |||||
| #include "NOD_node_tree_ref.hh" | |||||
| #include "NOD_texture.h" | |||||
| #include "NOD_texture_evaluate.hh" | |||||
| #include "FN_field.hh" | |||||
| #include "FN_field_cpp_type.hh" | |||||
| #include "FN_multi_function.hh" | |||||
| #include "FN_multi_function_builder.hh" | |||||
| #include "FN_multi_function_procedure_builder.hh" | |||||
| #include "FN_multi_function_procedure_executor.hh" | |||||
| #include "RE_texture.h" | |||||
| #include <memory> | |||||
| #include <optional> | |||||
| namespace blender::nodes { | |||||
| using namespace fn::multi_function_types; | |||||
| class PositionFieldInput : public fn::FieldInput { | |||||
| public: | |||||
| PositionFieldInput() : fn::FieldInput(CPPType::get<float3>()) | |||||
| { | |||||
| } | |||||
| GVArray get_varray_for_context(const fn::FieldContext & /* context */, | |||||
| const IndexMask /* mask */, | |||||
| ResourceScope & /* scope */) const | |||||
| { | |||||
| return {}; | |||||
| } | |||||
| }; | |||||
| class Texture2DFieldContext : public fn::FieldContext { | |||||
| private: | |||||
| const Span<float3> positions_; | |||||
| public: | |||||
| Texture2DFieldContext(const Span<float3> positions) : positions_(positions) | |||||
| { | |||||
| } | |||||
| GVArray get_varray_for_input(const fn::FieldInput &field_input, | |||||
| const IndexMask /* mask */, | |||||
| ResourceScope & /* scope */) const override | |||||
| { | |||||
| if (dynamic_cast<const PositionFieldInput *>(&field_input)) { | |||||
| return VArray<float3>::ForSpan(positions_); | |||||
| } | |||||
| return {}; | |||||
| } | |||||
| }; | |||||
| class TextureNodeEvaluator { | |||||
| private: | |||||
| /* Node tree used. */ | |||||
| bNodeTree &ntree_; | |||||
| /* Is execution possible? */ | |||||
| bool valid_ = true; | |||||
| /* Multi-functions. */ | |||||
| ResourceScope scope_; | |||||
| std::unique_ptr<NodeMultiFunctions> multi_functions_; | |||||
| /* Fields. */ | |||||
| Map<DSocket, GField> field_by_socket_; | |||||
| fn::Field<float> float_texture_field_; | |||||
| public: | |||||
| TextureNodeEvaluator(bNodeTree &node_tree) : ntree_(node_tree) | |||||
| { | |||||
| compile(); | |||||
| } | |||||
| bNodeTree &node_tree() | |||||
| { | |||||
| return ntree_; | |||||
| } | |||||
| bool valid() const | |||||
| { | |||||
| return valid_; | |||||
| } | |||||
| template<typename OutT> | |||||
| void execute_at_position(const float3 &position, int thread, OutT &output) | |||||
| { | |||||
| BLI_assert(thread < BLENDER_MAX_THREADS); | |||||
| BLI_assert(valid_); | |||||
| UNUSED_VARS(thread); | |||||
| this->execute_at_positions<OutT>({&position, 1}, IndexRange(1), {&output, 1}); | |||||
| } | |||||
| template<typename OutT> | |||||
| void execute_at_positions(const Span<float3> positions, | |||||
| const IndexMask mask, | |||||
| MutableSpan<OutT> r_values) | |||||
| { | |||||
| BLI_assert(valid_); | |||||
| std::cout << "Size: " << mask.size() << "\n"; | |||||
| SCOPED_TIMER(__func__); | |||||
| const fn::Field<OutT> field = bke::get_implicit_type_conversions().try_convert( | |||||
| float_texture_field_, CPPType::get<OutT>()); | |||||
| Texture2DFieldContext context{positions}; | |||||
| fn::FieldEvaluator evaluator{context, &mask}; | |||||
| evaluator.add_with_destination(field, r_values); | |||||
| evaluator.evaluate(); | |||||
| } | |||||
| void compile() | |||||
| { | |||||
| NodeTreeRefMap tree_refs; | |||||
| DerivedNodeTree tree(ntree_, tree_refs); | |||||
| if (tree.has_link_cycles()) { | |||||
| valid_ = false; | |||||
| return; | |||||
| } | |||||
| /* Get group output node. */ | |||||
| const DTreeContext *root_context = &tree.root_context(); | |||||
| const NodeTreeRef &root_tree_ref = root_context->tree(); | |||||
| Span<const NodeRef *> output_nodes = root_tree_ref.nodes_by_type("NodeGroupOutput"); | |||||
| if (output_nodes.size() != 1) { | |||||
| valid_ = false; | |||||
| return; | |||||
| } | |||||
| const NodeRef *output_node_ref = output_nodes[0]; | |||||
| const DNode output_node(root_context, output_node_ref); | |||||
| if (output_node->inputs().size() <= 1) { | |||||
| valid_ = false; | |||||
| return; | |||||
| } | |||||
| const DInputSocket group_output_socket = output_node.input(0); | |||||
| /* Create multi function for all nodes in tree. */ | |||||
| multi_functions_ = std::make_unique<NodeMultiFunctions>(tree); | |||||
| GField final_field = this->compile_field_for_input(group_output_socket); | |||||
| float_texture_field_ = bke::get_implicit_type_conversions().try_convert(final_field, | |||||
| CPPType::get<float>()); | |||||
| /* Stop if there was any error while compiling. */ | |||||
| if (!valid_) { | |||||
| return; | |||||
| } | |||||
| } | |||||
| GField compile_field_for_output(const DOutputSocket &output_socket) | |||||
| { | |||||
| BLI_assert(output_socket->is_available()); | |||||
| GField ret_field = field_by_socket_.lookup_default(output_socket, {}); | |||||
| if (ret_field) { | |||||
| return ret_field; | |||||
| } | |||||
| const CPPType *type = this->get_socket_cpp_type(*output_socket.socket_ref()); | |||||
| if (type == nullptr) { | |||||
| valid_ = false; | |||||
| return {}; | |||||
| } | |||||
| const DNode node = output_socket.node(); | |||||
| Vector<GField> input_fields; | |||||
| for (const InputSocketRef *input_socket_ref : node->inputs()) { | |||||
| const DInputSocket input_socket{node.context(), input_socket_ref}; | |||||
| if (input_socket->is_available()) { | |||||
| input_fields.append(this->compile_field_for_input(input_socket)); | |||||
| if (!input_fields.last()) { | |||||
| return {}; | |||||
| } | |||||
| } | |||||
| } | |||||
| const NodeMultiFunctions::Item &fn_item = multi_functions_->try_get(node); | |||||
| if (fn_item.fn != nullptr) { | |||||
| const MultiFunction &fn = *fn_item.fn; | |||||
| auto field_operation = std::make_shared<fn::FieldOperation>(fn, input_fields); | |||||
| int output_index = 0; | |||||
| for (const OutputSocketRef *output_socket_ref : node->outputs()) { | |||||
| if (output_socket_ref->is_available()) { | |||||
| const DOutputSocket socket{output_socket.context(), output_socket_ref}; | |||||
| GField output_field{field_operation, output_index}; | |||||
| field_by_socket_.add_new(socket, output_field); | |||||
| output_index++; | |||||
| } | |||||
| } | |||||
| } | |||||
| else { | |||||
| if (!this->execute_input_node(node)) { | |||||
| valid_ = false; | |||||
| return {}; | |||||
| } | |||||
| } | |||||
| return field_by_socket_.lookup(output_socket); | |||||
| } | |||||
| GField compile_field_for_input(const DInputSocket &input_socket) | |||||
| { | |||||
| BLI_assert(input_socket->is_available()); | |||||
| GField ret_field = field_by_socket_.lookup_default(input_socket, {}); | |||||
| if (ret_field) { | |||||
| return ret_field; | |||||
| } | |||||
| const CPPType *input_type = this->get_socket_cpp_type(*input_socket.socket_ref()); | |||||
| if (input_type == nullptr) { | |||||
| valid_ = false; | |||||
| return {}; | |||||
| } | |||||
| input_socket.foreach_origin_socket([&](const DSocket origin_socket) { | |||||
| if (origin_socket->is_input()) { | |||||
| const DInputSocket origin_input_socket(origin_socket); | |||||
| ret_field = this->compile_field_for_input(origin_input_socket); | |||||
| } | |||||
| else { | |||||
| const DOutputSocket origin_output_socket(origin_socket); | |||||
| ret_field = this->compile_field_for_output(origin_output_socket); | |||||
| } | |||||
| }); | |||||
| if (!ret_field) { | |||||
| BUFFER_FOR_CPP_TYPE_VALUE(*input_type, buffer); | |||||
| input_socket->typeinfo()->get_geometry_nodes_cpp_value(*input_socket->bsocket(), buffer); | |||||
| ret_field = fn::make_constant_field(*input_type, buffer); | |||||
| input_type->destruct(buffer); | |||||
| } | |||||
| if (ret_field.cpp_type() != *input_type) { | |||||
| ret_field = bke::get_implicit_type_conversions().try_convert(ret_field, *input_type); | |||||
| } | |||||
| field_by_socket_.add_new(input_socket, ret_field); | |||||
| return ret_field; | |||||
| } | |||||
| /* Get socket CPP type. TODO: dedup with geometry nodes? */ | |||||
| const CPPType *get_socket_cpp_type(const SocketRef &socket) | |||||
| { | |||||
| const bNodeSocketType *typeinfo = socket.typeinfo(); | |||||
| if (typeinfo->geometry_nodes_cpp_type == nullptr) { | |||||
| return nullptr; | |||||
| } | |||||
| const CPPType *type = typeinfo->base_cpp_type; | |||||
| if (type == nullptr) { | |||||
| return nullptr; | |||||
| } | |||||
| /* The evaluator only supports types that have special member functions. */ | |||||
| if (!type->has_special_member_functions()) { | |||||
| return nullptr; | |||||
| } | |||||
| return type; | |||||
| } | |||||
| bool execute_input_node(const DNode &node) | |||||
| { | |||||
| switch (node->bnode()->type) { | |||||
| case SH_NODE_NEW_GEOMETRY: { | |||||
| field_by_socket_.add_new(node.output_by_identifier("Position"), | |||||
| {std::make_shared<PositionFieldInput>()}); | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| /* Evaluator lookup and creation. */ | |||||
| static TextureNodeEvaluator *get_evaluator(bNodeTree *ntree) | |||||
| { | |||||
| /* Ensure execdata is only initialized once. */ | |||||
| bNodeTreeExec *exec = ntree->execdata; | |||||
| if (!exec) { | |||||
| BLI_thread_lock(LOCK_NODES); | |||||
| if (!ntree->execdata) { | |||||
| ntreeTexBeginExecTree(ntree); | |||||
| } | |||||
| BLI_thread_unlock(LOCK_NODES); | |||||
| exec = ntree->execdata; | |||||
| } | |||||
| return (TextureNodeEvaluator *)exec; | |||||
| } | |||||
| static TextureNodeEvaluator *get_evaluator(const Tex &tex) | |||||
| { | |||||
| if (!tex.use_nodes && tex.nodetree) { | |||||
| return nullptr; | |||||
| } | |||||
| return get_evaluator(tex.nodetree); | |||||
| } | |||||
| /* Single point evaluation. */ | |||||
| bool texture_evaluate_at_position(const Tex &texture, | |||||
| const float3 &position, | |||||
| const int thread, | |||||
| float *r_value) | |||||
| { | |||||
| TextureNodeEvaluator *evaluator = get_evaluator(texture); | |||||
| if (evaluator && evaluator->valid()) { | |||||
| evaluator->execute_at_position(position, thread, *r_value); | |||||
| return true; | |||||
| } | |||||
| memset(r_value, 0, sizeof(*r_value)); | |||||
| return false; | |||||
| } | |||||
| bool texture_evaluate_at_position(const Tex &texture, | |||||
| const float3 &position, | |||||
| const int thread, | |||||
| ColorGeometry4f *r_value) | |||||
| { | |||||
| TextureNodeEvaluator *evaluator = get_evaluator(texture); | |||||
| if (evaluator && evaluator->valid()) { | |||||
| evaluator->execute_at_position(position, thread, *r_value); | |||||
| return true; | |||||
| } | |||||
| memset(static_cast<void *>(r_value), 0, sizeof(*r_value)); | |||||
| return false; | |||||
| } | |||||
| void texture_evaluate_at_positions(const Tex &texture, | |||||
| blender::Span<blender::float3> positions, | |||||
| blender::IndexMask mask, | |||||
| blender::MutableSpan<float> r_values) | |||||
| { | |||||
| TextureNodeEvaluator *evaluator = get_evaluator(texture); | |||||
| if (evaluator && evaluator->valid()) { | |||||
| evaluator->execute_at_positions(positions, mask, r_values); | |||||
| } | |||||
| else { | |||||
| r_values.fill_indices(mask, 0.0f); | |||||
| } | |||||
| } | |||||
| void texture_evaluate_at_positions(const Tex &texture, | |||||
| blender::Span<blender::float3> positions, | |||||
| blender::IndexMask mask, | |||||
| blender::MutableSpan<blender::ColorGeometry4f> r_values) | |||||
| { | |||||
| TextureNodeEvaluator *evaluator = get_evaluator(texture); | |||||
| if (evaluator && evaluator->valid()) { | |||||
| evaluator->execute_at_positions(positions, mask, r_values); | |||||
| } | |||||
| else { | |||||
| r_values.fill_indices(mask, {0.0f, 0.0f, 0.0f, 1.0f}); | |||||
| } | |||||
| } | |||||
| } // namespace blender::nodes | |||||
| /* TODO: Temporary hack to override existing texture nodes. */ | |||||
| bNodeTreeExec *ntreeTexBeginExecTree(bNodeTree *ntree) | |||||
| { | |||||
| if (!ntree->execdata) { | |||||
| ntree->execdata = (bNodeTreeExec *)(new blender::nodes::TextureNodeEvaluator(*ntree)); | |||||
| } | |||||
| return ntree->execdata; | |||||
| } | |||||
| void ntreeTexEndExecTree(bNodeTreeExec *exec) | |||||
| { | |||||
| if (exec) { | |||||
| blender::nodes::TextureNodeEvaluator *evaluator = (blender::nodes::TextureNodeEvaluator *)exec; | |||||
| evaluator->node_tree().execdata = nullptr; | |||||
| delete evaluator; | |||||
| } | |||||
| } | |||||
| int ntreeTexExecTree(bNodeTree *ntree, | |||||
| TexResult *texres, | |||||
| const float co[3], | |||||
| float UNUSED(dxt[3]), | |||||
| float UNUSED(dyt[3]), | |||||
| int UNUSED(osatex), | |||||
| const short thread, | |||||
| const Tex *UNUSED(tex), | |||||
| short UNUSED(which_output), | |||||
| int UNUSED(cfra), | |||||
| int UNUSED(preview), | |||||
| MTex *UNUSED(mtex)) | |||||
| { | |||||
| blender::nodes::TextureNodeEvaluator *evaluator = blender::nodes::get_evaluator(ntree); | |||||
| if (evaluator && evaluator->valid()) { | |||||
| evaluator->execute_at_position(blender::float3(co), thread, texres->tin); | |||||
| } | |||||
| else { | |||||
| texres->tin = 0.0f; | |||||
| } | |||||
| texres->trgba[0] = texres->tin; | |||||
| texres->trgba[1] = texres->tin; | |||||
| texres->trgba[2] = texres->tin; | |||||
| texres->trgba[3] = 1.0f; | |||||
| texres->talpha = 1.0f; | |||||
| return TEX_INT; | |||||
| } | |||||