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_texture_field.hh" | |||||
| #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; | |||||
| /* Dummy provider so we can execute geometry nodes functions. */ | |||||
| class TextureNodeParamsProvider : public nodes::GeoNodeExecParamsProvider { | |||||
| private: | |||||
| LinearAllocator<> &allocator_; | |||||
| Vector<GMutablePointer> output_values_; | |||||
| public: | |||||
| TextureNodeParamsProvider(DNode dnode, LinearAllocator<> &allocator) | |||||
| : allocator_(allocator), output_values_(dnode->outputs().size()) | |||||
| { | |||||
| this->dnode = dnode; | |||||
| this->self_object = nullptr; | |||||
| this->modifier = nullptr; | |||||
| this->depsgraph = nullptr; | |||||
| this->logger = nullptr; | |||||
| } | |||||
| const Vector<GMutablePointer> &output_values() | |||||
| { | |||||
| return output_values_; | |||||
| } | |||||
| bool can_get_input(StringRef /*identifier*/) const override | |||||
| { | |||||
| return false; | |||||
| } | |||||
| bool can_set_output(StringRef /*identifier*/) const override | |||||
| { | |||||
| return true; | |||||
| } | |||||
| GMutablePointer extract_input(StringRef /*identifier*/) override | |||||
| { | |||||
| return GMutablePointer(); | |||||
| } | |||||
| Vector<GMutablePointer> extract_multi_input(StringRef /*identifier*/) override | |||||
| { | |||||
| return Vector<GMutablePointer>(); | |||||
| } | |||||
| GPointer get_input(StringRef /*identifier*/) const override | |||||
| { | |||||
| return GPointer(); | |||||
| } | |||||
| GMutablePointer alloc_output_value(const CPPType &type) override | |||||
| { | |||||
| return {type, allocator_.allocate(type.size(), type.alignment())}; | |||||
| } | |||||
| void set_output(StringRef identifier, GMutablePointer value) override | |||||
| { | |||||
| const DOutputSocket socket = this->dnode.output_by_identifier(identifier); | |||||
| output_values_[socket->index()] = value; | |||||
| } | |||||
| void set_input_unused(StringRef /*identifier*/) override | |||||
| { | |||||
| } | |||||
| bool output_is_required(StringRef /*identifier*/) const override | |||||
| { | |||||
| /* TODO: implement for better compilation performance. */ | |||||
| return true; | |||||
| } | |||||
| bool lazy_require_input(StringRef /*identifier*/) override | |||||
| { | |||||
| return false; | |||||
| } | |||||
| bool lazy_output_is_required(StringRef /*identifier*/) const override | |||||
| { | |||||
| return true; | |||||
| } | |||||
| void set_default_remaining_outputs() override | |||||
| { | |||||
| } | |||||
| }; | |||||
| class TextureNodeEvaluator { | |||||
| private: | |||||
| /* Node tree used. */ | |||||
| bNodeTree &ntree_; | |||||
| /* Is execution possible? */ | |||||
| bool valid_ = true; | |||||
| /* Multi-functions. */ | |||||
| ResourceScope scope_; | |||||
| std::unique_ptr<NodeMultiFunctions> multi_functions_; | |||||
| Vector<std::unique_ptr<fn::CustomMF_GenericConstant>> constant_functions_; | |||||
| fn::MFProcedure procedure_; | |||||
| /* Fields. */ | |||||
| Vector<fn::GField> input_fields_; | |||||
| Vector<fn::MFVariable *> input_field_variables_; | |||||
| /* Executor. */ | |||||
| std::unique_ptr<fn::MFProcedureExecutor> executor_; | |||||
| /* Cached data for single executions, until everything is converted to batched. */ | |||||
| TextureBatch single_batch_; | |||||
| std::unique_ptr<fn::MFParamsBuilder> single_params_; | |||||
| Vector<float> single_output_values_; | |||||
| /* Temporary state during compilation. */ | |||||
| struct CompiledNodeState { | |||||
| Vector<fn::MFVariable *> variables; | |||||
| Vector<int> fields; | |||||
| }; | |||||
| Map<DNode, CompiledNodeState> compiled_nodes_; | |||||
| public: | |||||
| TextureNodeEvaluator(bNodeTree &node_tree) | |||||
| : ntree_(node_tree), single_batch_(nullptr, BLENDER_MAX_THREADS) | |||||
| { | |||||
| compile(); | |||||
| } | |||||
| bNodeTree &node_tree() | |||||
| { | |||||
| return ntree_; | |||||
| } | |||||
| bool valid() const | |||||
| { | |||||
| return valid_; | |||||
| } | |||||
| template<typename OutT> void execute_single(const float3 position, int thread, OutT &output) | |||||
| { | |||||
| BLI_assert(thread < BLENDER_MAX_THREADS); | |||||
| BLI_assert(valid_); | |||||
| single_batch_.positions[thread] = position; | |||||
| fn::MFContextBuilder context_builder; | |||||
| fn::MFContext context(context_builder); | |||||
| IndexRange range(thread, 1); | |||||
| executor_->call(range, *single_params_, context); | |||||
| set_execute_output(single_output_values_, thread, output); | |||||
| } | |||||
| template<typename BatchT, typename OutT> | |||||
| void execute_batch(const BatchT &batch, const MutableSpan<OutT> &output, IndexMask mask) | |||||
| { | |||||
| BLI_assert(valid_); | |||||
| fn::MFContextBuilder context_builder; | |||||
| fn::MFContext context(context_builder); | |||||
| Vector<float> output_values(batch.size); | |||||
| ResourceScope local_scope; | |||||
| /* TODO: cache some of this work between executions? */ | |||||
| fn::MFParamsBuilder params(*executor_, batch.size); | |||||
| set_execute_input_params(batch, local_scope, params); | |||||
| set_execute_output_params(batch, params, output_values); | |||||
| executor_->call(mask, params, context); | |||||
| for (const int i : mask) { | |||||
| set_execute_output(output_values, i, output[i]); | |||||
| } | |||||
| } | |||||
| private: | |||||
| template<typename FieldContextT> | |||||
| void set_execute_input_params(const TextureBatch &batch, | |||||
| FieldContextT &field_context, | |||||
| ResourceScope &scope, | |||||
| fn::MFParamsBuilder ¶ms) | |||||
| { | |||||
| Vector<fn::GFieldRef> input_field_refs; | |||||
| for (const int i : input_fields_.index_range()) { | |||||
| if (input_field_variables_[i]) { | |||||
| input_field_refs.append(input_fields_[i]); | |||||
| } | |||||
| } | |||||
| Vector<GVArray> gvarrays = fn::evaluate_fields( | |||||
| scope, input_field_refs, IndexRange(batch.size), field_context); | |||||
| for (const GVArray &gvarray : gvarrays) { | |||||
| params.add_readonly_single_input(gvarray); /* TODO: std::move somehow? */ | |||||
| } | |||||
| } | |||||
| void set_execute_input_params(const GeometryDomainTextureBatch &batch, | |||||
| ResourceScope &scope, | |||||
| fn::MFParamsBuilder ¶ms) | |||||
| { | |||||
| if (GS(batch.geometry->name) == ID_ME) { | |||||
| MeshComponent component; | |||||
| component.replace((Mesh *)batch.geometry, GeometryOwnershipType::ReadOnly); | |||||
| GeometryComponentFieldContext field_context(component, batch.domain); | |||||
| set_execute_input_params(batch, field_context, scope, params); | |||||
| } | |||||
| else if (GS(batch.geometry->name) == ID_CV) { | |||||
| CurveComponent component; | |||||
| component.replace((Curves *)batch.geometry, GeometryOwnershipType::ReadOnly); | |||||
| GeometryComponentFieldContext field_context(component, batch.domain); | |||||
| set_execute_input_params(batch, field_context, scope, params); | |||||
| } | |||||
| else if (GS(batch.geometry->name) == ID_PT) { | |||||
| PointCloudComponent component; | |||||
| component.replace((PointCloud *)batch.geometry, GeometryOwnershipType::ReadOnly); | |||||
| GeometryComponentFieldContext field_context(component, batch.domain); | |||||
| set_execute_input_params(batch, field_context, scope, params); | |||||
| } | |||||
| else if (GS(batch.geometry->name) == ID_VO) { | |||||
| VolumeComponent component; | |||||
| component.replace((Volume *)batch.geometry, GeometryOwnershipType::ReadOnly); | |||||
| GeometryComponentFieldContext field_context(component, batch.domain); | |||||
| set_execute_input_params(batch, field_context, scope, params); | |||||
| } | |||||
| else { | |||||
| BLI_assert_unreachable(); | |||||
| } | |||||
| } | |||||
| void set_execute_input_params(const GeometrySurfaceTextureBatch &batch, | |||||
| ResourceScope &scope, | |||||
| fn::MFParamsBuilder ¶ms) | |||||
| { | |||||
| bke::TextureFieldContext field_context(batch); | |||||
| set_execute_input_params(batch, field_context, scope, params); | |||||
| } | |||||
| void set_execute_input_params(const TextureBatch &batch, | |||||
| ResourceScope &scope, | |||||
| fn::MFParamsBuilder ¶ms) | |||||
| { | |||||
| bke::TextureFieldContext field_context(batch); | |||||
| set_execute_input_params(batch, field_context, scope, params); | |||||
| } | |||||
| void set_execute_output_params(const TextureBatch &batch, | |||||
| fn::MFParamsBuilder ¶ms, | |||||
| Vector<float> &output_values) | |||||
| { | |||||
| output_values.resize(batch.size); | |||||
| params.add_uninitialized_single_output(output_values.as_mutable_span()); | |||||
| } | |||||
| void set_execute_output(Vector<float> &output_values, int index, float &result) | |||||
| { | |||||
| result = output_values[index]; | |||||
| } | |||||
| void set_execute_output(Vector<float> &output_values, int index, float4 &result) | |||||
| { | |||||
| /* TODO: RGBA output. */ | |||||
| result = float4(output_values[index], output_values[index], output_values[index], 1.0f); | |||||
| } | |||||
| 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); | |||||
| /* Create multi function for all nodes in tree. */ | |||||
| multi_functions_ = std::make_unique<NodeMultiFunctions>(tree); | |||||
| /* Compile procedure for inputs of group output node. */ | |||||
| fn::MFProcedureBuilder builder(procedure_); | |||||
| Vector<fn::MFVariable *> output_variables; | |||||
| for (const InputSocketRef *socket_ref : output_node_ref->inputs()) { | |||||
| DInputSocket input_socket(root_context, socket_ref); | |||||
| bool is_constant; | |||||
| fn::MFVariable *variable = compile_node_input(builder, tree, input_socket, is_constant); | |||||
| if (variable) { | |||||
| output_variables.append(variable); | |||||
| builder.add_output_parameter(*variable); | |||||
| } | |||||
| /* TODO: multiple outputs, convert data type. */ | |||||
| break; | |||||
| } | |||||
| /* Destruct temporary variables from node outputs */ | |||||
| for (const CompiledNodeState &state : compiled_nodes_.values()) { | |||||
| for (fn::MFVariable *variable : state.variables) { | |||||
| if (variable && !output_variables.contains(variable)) { | |||||
| builder.add_destruct(*variable); | |||||
| } | |||||
| } | |||||
| } | |||||
| compiled_nodes_.clear(); | |||||
| builder.add_return(); | |||||
| /* Stop if there was any error while compiling. */ | |||||
| if (!valid_) { | |||||
| return; | |||||
| } | |||||
| if (output_variables.size() == 0) { | |||||
| valid_ = false; | |||||
| return; | |||||
| } | |||||
| executor_ = std::make_unique<fn::MFProcedureExecutor>(procedure_); | |||||
| single_params_ = std::make_unique<fn::MFParamsBuilder>(*executor_, single_batch_.size); | |||||
| set_execute_input_params(single_batch_, scope_, *single_params_); | |||||
| set_execute_output_params(single_batch_, *single_params_, single_output_values_); | |||||
| } | |||||
| /* Get variable going into this node input, compiling linked nodes as needed. */ | |||||
| fn::MFVariable *compile_node_input(fn::MFProcedureBuilder &builder, | |||||
| const DerivedNodeTree &tree, | |||||
| const DInputSocket &input_socket, | |||||
| bool &is_constant) | |||||
| { | |||||
| std::optional<fn::MFVariable *> value; | |||||
| is_constant = false; | |||||
| input_socket.foreach_origin_socket([&](const DSocket origin_socket) { | |||||
| if (origin_socket->is_input()) { | |||||
| /* Connected to another input, for example a group input. */ | |||||
| DInputSocket origin_input_socket(origin_socket); | |||||
| value = get_socket_value(builder, *(origin_input_socket.socket_ref())); | |||||
| is_constant = true; | |||||
| } | |||||
| else { | |||||
| /* Connected to an output, need to compile node. */ | |||||
| DOutputSocket origin_output_socket(origin_socket); | |||||
| value = compile_node_output(builder, tree, origin_output_socket); | |||||
| } | |||||
| return; | |||||
| }); | |||||
| if (!value) { | |||||
| /* Not connected, use value from input itself. */ | |||||
| value = get_socket_value(builder, *(input_socket.socket_ref())); | |||||
| is_constant = true; | |||||
| } | |||||
| return *value; | |||||
| } | |||||
| /* Get variable corresponding to node output, for a node that is already compiled. */ | |||||
| fn::MFVariable *get_node_output_variable(fn::MFProcedureBuilder &builder, | |||||
| const DOutputSocket &doutput_socket, | |||||
| CompiledNodeState &state) | |||||
| { | |||||
| const NodeRef &node = *doutput_socket.node().node_ref(); | |||||
| int variable_index = 0; | |||||
| for (const int i : node.outputs().index_range()) { | |||||
| const OutputSocketRef &socket = node.output(i); | |||||
| if (socket.is_available()) { | |||||
| if (doutput_socket.socket_ref() == &socket) { | |||||
| if (state.variables[variable_index] == nullptr) { | |||||
| /* Create input parameter for field on demand, only for sockets that are used. */ | |||||
| if (state.fields.is_empty()) { | |||||
| BLI_assert_unreachable(); | |||||
| break; | |||||
| } | |||||
| const int field_index = state.fields[variable_index]; | |||||
| if (input_field_variables_[field_index] == nullptr) { | |||||
| fn::GField &field = input_fields_[field_index]; | |||||
| fn::MFDataType field_data_type = fn::MFDataType::ForSingle(field.cpp_type()); | |||||
| fn::MFVariable &field_variable = builder.add_input_parameter(field_data_type); | |||||
| input_field_variables_[field_index] = &field_variable; | |||||
| } | |||||
| state.variables[variable_index] = input_field_variables_[field_index]; | |||||
| } | |||||
| return state.variables[variable_index]; | |||||
| } | |||||
| variable_index++; | |||||
| } | |||||
| } | |||||
| BLI_assert_unreachable(); | |||||
| valid_ = false; | |||||
| return nullptr; | |||||
| } | |||||
| /* Get variable corresponding to node output, and compile node if needed. */ | |||||
| fn::MFVariable *compile_node_output(fn::MFProcedureBuilder &builder, | |||||
| const DerivedNodeTree &tree, | |||||
| const DOutputSocket &doutput_socket) | |||||
| { | |||||
| const DNode &dnode = doutput_socket.node(); | |||||
| const NodeRef &node = *dnode.node_ref(); | |||||
| /* Test if node was already compiled, if so just return output value. */ | |||||
| CompiledNodeState *existing_state = compiled_nodes_.lookup_ptr(dnode); | |||||
| if (existing_state) { | |||||
| return get_node_output_variable(builder, doutput_socket, *existing_state); | |||||
| } | |||||
| CompiledNodeState state; | |||||
| if (node.typeinfo()->geometry_node_execute) { | |||||
| /* General geometry node outputting fields, though for textures only a subset | |||||
| * will actually work since we don't provide a full context. */ | |||||
| BLI_assert(node.inputs().size() == 0); | |||||
| LinearAllocator<> &allocator = scope_.linear_allocator(); | |||||
| TextureNodeParamsProvider provider(dnode, allocator); | |||||
| GeoNodeExecParams params(provider); | |||||
| node.typeinfo()->geometry_node_execute(params); | |||||
| for (const int i : node.outputs().index_range()) { | |||||
| const OutputSocketRef &socket = node.output(i); | |||||
| if (socket.is_available()) { | |||||
| GMutablePointer output_value = provider.output_values()[i]; | |||||
| if (output_value.get()) { | |||||
| const fn::ValueOrFieldCPPType &field_cpp_type = | |||||
| static_cast<const fn::ValueOrFieldCPPType &>(*(output_value.type())); | |||||
| if (field_cpp_type.is_field(output_value.get())) { | |||||
| /* Field output, store the field to be used as parameter for execution. */ | |||||
| fn::GField field = field_cpp_type.as_field(output_value.get()); | |||||
| /* Only store the same field once. */ | |||||
| bool found_existing = false; | |||||
| for (const int i : input_fields_.index_range()) { | |||||
| if (input_fields_[i] == field) { | |||||
| found_existing = true; | |||||
| state.fields.append(i); | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (!found_existing) { | |||||
| /* Add new field, variables for it will only be created if used. */ | |||||
| state.fields.append(input_fields_.size()); | |||||
| input_fields_.append(std::move(field)); | |||||
| input_field_variables_.append(nullptr); | |||||
| } | |||||
| /* Will be initialized on demand in get_node_output_variable. */ | |||||
| state.variables.append(nullptr); | |||||
| } | |||||
| else { | |||||
| /* Value output. */ | |||||
| state.variables.append( | |||||
| compile_constant(builder, *output_value.type(), output_value.get())); | |||||
| state.fields.append(-1); | |||||
| } | |||||
| } | |||||
| else { | |||||
| /* No output for this socket. */ | |||||
| state.variables.append(nullptr); | |||||
| state.fields.append(-1); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| else { | |||||
| /* Multi-function node. */ | |||||
| /* Test if node can be compiled. */ | |||||
| const NodeMultiFunctions::Item &fn_item = multi_functions_->try_get(dnode); | |||||
| if (fn_item.fn == nullptr) { | |||||
| BLI_assert_unreachable(); | |||||
| valid_ = false; | |||||
| return nullptr; | |||||
| } | |||||
| const DTreeContext *root_context = &tree.root_context(); | |||||
| /* Gather input variables, and recursively compile nodes linked to input. | |||||
| * TODO: use stack to avoid running out of stack space? */ | |||||
| Vector<fn::MFVariable *> input_variables, destruct_variables; | |||||
| for (const int i : node.inputs().index_range()) { | |||||
| const InputSocketRef &socket = node.input(i); | |||||
| if (!socket.is_available()) { | |||||
| continue; | |||||
| } | |||||
| DInputSocket input_socket(root_context, &socket); | |||||
| bool is_constant; | |||||
| fn::MFVariable *variable = compile_node_input(builder, tree, input_socket, is_constant); | |||||
| if (variable == nullptr) { | |||||
| BLI_assert_unreachable(); | |||||
| valid_ = false; | |||||
| return nullptr; | |||||
| } | |||||
| const CPPType &socket_type = *get_socket_cpp_type(socket); | |||||
| const fn::ValueOrFieldCPPType &socket_cpp_type = | |||||
| static_cast<const fn::ValueOrFieldCPPType &>(socket_type); | |||||
| const CPPType &socket_base_type = socket_cpp_type.base_type(); | |||||
| fn::MFDataType socket_data_type = fn::MFDataType::ForSingle(socket_base_type); | |||||
| fn::MFDataType variable_data_type = variable->data_type(); | |||||
| /* Type conversions. */ | |||||
| if (socket_data_type != variable_data_type) { | |||||
| const blender::bke::DataTypeConversions &conversions = | |||||
| bke::get_implicit_type_conversions(); | |||||
| const fn::MultiFunction *conversion_fn = conversions.get_conversion_multi_function( | |||||
| variable_data_type, socket_data_type); | |||||
| fn::MFVariable *convert_variable = | |||||
| (conversion_fn) ? builder.add_call(*conversion_fn, Span(&variable, 1)).first() : | |||||
| get_socket_value(builder, socket, false); | |||||
| input_variables.append(convert_variable); | |||||
| destruct_variables.append(convert_variable); | |||||
| if (is_constant) { | |||||
| builder.add_destruct(*variable); | |||||
| } | |||||
| } | |||||
| else { | |||||
| input_variables.append(variable); | |||||
| if (is_constant) { | |||||
| destruct_variables.append(variable); | |||||
| } | |||||
| } | |||||
| } | |||||
| /* Add call to node multi-function. */ | |||||
| state.variables = builder.add_call(*(fn_item.fn), input_variables); | |||||
| builder.add_destruct(destruct_variables); | |||||
| } | |||||
| /* Return variable for output that triggered this node to be compiled. */ | |||||
| fn::MFVariable *output_variable = get_node_output_variable(builder, doutput_socket, state); | |||||
| /* Add to compiled nodes. */ | |||||
| compiled_nodes_.add(dnode, std::move(state)); | |||||
| return output_variable; | |||||
| } | |||||
| fn::MFVariable *compile_constant(fn::MFProcedureBuilder &builder, | |||||
| const CPPType &cpp_type, | |||||
| const void *buffer) | |||||
| { | |||||
| /* TODO: is a constant multi-function efficient enough, is it going to be called | |||||
| * for every execution? */ | |||||
| constant_functions_.append( | |||||
| std::make_unique<fn::CustomMF_GenericConstant>(cpp_type, buffer, false)); | |||||
| return builder.add_call(*constant_functions_.last()).first(); | |||||
| } | |||||
| /* 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->geometry_nodes_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; | |||||
| } | |||||
| /* Get fixed socket value. */ | |||||
| fn::MFVariable *get_socket_value(fn::MFProcedureBuilder &builder, | |||||
| const InputSocketRef &socket, | |||||
| const bool use_node_value = true) | |||||
| { | |||||
| LinearAllocator<> &allocator = scope_.linear_allocator(); | |||||
| const CPPType &type = *get_socket_cpp_type(socket); | |||||
| void *buffer = allocator.allocate(type.size(), type.alignment()); | |||||
| const fn::ValueOrFieldCPPType &socket_cpp_type = static_cast<const fn::ValueOrFieldCPPType &>( | |||||
| type); | |||||
| const CPPType &socket_base_type = socket_cpp_type.base_type(); | |||||
| if (use_node_value) { | |||||
| socket.typeinfo()->get_geometry_nodes_cpp_value(*socket.bsocket(), buffer); | |||||
| } | |||||
| else { | |||||
| socket_base_type.value_initialize(buffer); | |||||
| } | |||||
| return compile_constant(builder, socket_base_type, buffer); | |||||
| } | |||||
| }; // namespace blender::nodes | |||||
| /* 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); | |||||
| } | |||||
| /* Batches. */ | |||||
| TextureBatch::TextureBatch(const Tex *tex, const int size) : tex(tex), size(size), positions(size) | |||||
| { | |||||
| } | |||||
| void TextureBatch::evaluate(const MutableSpan<float> &output, IndexMask mask) const | |||||
| { | |||||
| BLI_assert(output.size() == positions.size()); | |||||
| TextureNodeEvaluator *evaluator = get_evaluator(tex); | |||||
| if (evaluator && evaluator->valid()) { | |||||
| evaluator->execute_batch(*this, output, mask); | |||||
| } | |||||
| else { | |||||
| memset(output.data(), 0, output.size_in_bytes()); | |||||
| } | |||||
| } | |||||
| void TextureBatch::evaluate(const MutableSpan<float4> &output, IndexMask mask) const | |||||
| { | |||||
| BLI_assert(output.size() == positions.size()); | |||||
| TextureNodeEvaluator *evaluator = get_evaluator(tex); | |||||
| if (evaluator && evaluator->valid()) { | |||||
| evaluator->execute_batch(*this, output, mask); | |||||
| } | |||||
| else { | |||||
| memset(output.data(), 0, output.size_in_bytes()); | |||||
| } | |||||
| } | |||||
| GeometryDomainTextureBatch::GeometryDomainTextureBatch(const Tex *tex, | |||||
| const ID *geometry, | |||||
| eAttrDomain domain) | |||||
| : TextureBatch(tex, BKE_id_attribute_domain_length(geometry, domain)), | |||||
| geometry(geometry), | |||||
| domain(domain) | |||||
| { | |||||
| } | |||||
| void GeometryDomainTextureBatch::evaluate(const MutableSpan<float> &output) const | |||||
| { | |||||
| BLI_assert(output.size() == positions.size()); | |||||
| TextureNodeEvaluator *evaluator = get_evaluator(tex); | |||||
| if (evaluator && evaluator->valid()) { | |||||
| evaluator->execute_batch(*this, output, IndexRange(size)); | |||||
| } | |||||
| else { | |||||
| memset(output.data(), 0, output.size_in_bytes()); | |||||
| } | |||||
| } | |||||
| void GeometryDomainTextureBatch::evaluate(const MutableSpan<float4> &output) const | |||||
| { | |||||
| BLI_assert(output.size() == positions.size()); | |||||
| TextureNodeEvaluator *evaluator = get_evaluator(tex); | |||||
| if (evaluator && evaluator->valid()) { | |||||
| evaluator->execute_batch(*this, output, IndexRange(size)); | |||||
| } | |||||
| else { | |||||
| memset(output.data(), 0, output.size_in_bytes()); | |||||
| } | |||||
| } | |||||
| GeometrySurfaceTextureBatch::GeometrySurfaceTextureBatch(const Tex *tex, | |||||
| const Mesh *mesh, | |||||
| const int size) | |||||
| : TextureBatch(tex, size), mesh(mesh), samples(size) | |||||
| { | |||||
| } | |||||
| void GeometrySurfaceTextureBatch::evaluate(const MutableSpan<float> &output) const | |||||
| { | |||||
| BLI_assert(output.size() == positions.size()); | |||||
| TextureNodeEvaluator *evaluator = get_evaluator(tex); | |||||
| if (evaluator && evaluator->valid()) { | |||||
| evaluator->execute_batch(*this, output, IndexRange(size)); | |||||
| } | |||||
| else { | |||||
| memset(output.data(), 0, output.size_in_bytes()); | |||||
| } | |||||
| } | |||||
| void GeometrySurfaceTextureBatch::evaluate(const MutableSpan<float4> &output) const | |||||
| { | |||||
| BLI_assert(output.size() == positions.size()); | |||||
| TextureNodeEvaluator *evaluator = get_evaluator(tex); | |||||
| if (evaluator && evaluator->valid()) { | |||||
| evaluator->execute_batch(*this, output, IndexRange(size)); | |||||
| } | |||||
| else { | |||||
| memset(output.data(), 0, output.size_in_bytes()); | |||||
| } | |||||
| } | |||||
| /* Single point evaluation. */ | |||||
| bool texture_evaluate_single(Tex *tex, const float3 position, const int thread, float *output) | |||||
| { | |||||
| TextureNodeEvaluator *evaluator = get_evaluator(tex); | |||||
| if (evaluator && evaluator->valid()) { | |||||
| evaluator->execute_single(position, thread, *output); | |||||
| return true; | |||||
| } | |||||
| else { | |||||
| memset(output, 0, sizeof(*output)); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| bool texture_evaluate_single(Tex *tex, const float3 position, const int thread, float4 *output) | |||||
| { | |||||
| TextureNodeEvaluator *evaluator = get_evaluator(tex); | |||||
| if (evaluator && evaluator->valid()) { | |||||
| evaluator->execute_single(position, thread, *output); | |||||
| return true; | |||||
| } | |||||
| else { | |||||
| memset(output, 0, sizeof(*output)); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| } // 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_single(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; | |||||
| } | |||||