Split VoxelGraphRuntime into several files
This commit is contained in:
parent
0ebf8f764e
commit
824a2955a9
@ -1346,18 +1346,8 @@ void VoxelGeneratorGraph::bake_sphere_normalmap(Ref<Image> im, float ref_radius,
|
||||
String VoxelGeneratorGraph::generate_shader() {
|
||||
ZN_PROFILE_SCOPE();
|
||||
|
||||
std::shared_ptr<const Runtime> runtime_ptr;
|
||||
{
|
||||
RWLockRead rlock(_runtime_lock);
|
||||
runtime_ptr = _runtime;
|
||||
}
|
||||
|
||||
ZN_ASSERT_RETURN_V(runtime_ptr != nullptr, "");
|
||||
ZN_ASSERT_RETURN_V_MSG(
|
||||
runtime_ptr->sdf_output_buffer_index != -1, "", "This function only works with an SDF output.");
|
||||
|
||||
std::string code_utf8;
|
||||
VoxelGraphRuntime::CompilationResult result = runtime_ptr->runtime.generate_shader(_graph, code_utf8);
|
||||
VoxelGraphRuntime::CompilationResult result = zylann::voxel::generate_shader(_graph, code_utf8);
|
||||
|
||||
ERR_FAIL_COND_V_MSG(!result.success, "", result.message);
|
||||
|
||||
|
692
generators/graph/voxel_graph_compiler.cpp
Normal file
692
generators/graph/voxel_graph_compiler.cpp
Normal file
@ -0,0 +1,692 @@
|
||||
#include "voxel_graph_compiler.h"
|
||||
#include "../../util/container_funcs.h"
|
||||
#include "../../util/expression_parser.h"
|
||||
#include "../../util/macros.h"
|
||||
#include "../../util/profiling.h"
|
||||
#include "../../util/string_funcs.h"
|
||||
#include "voxel_graph_node_db.h"
|
||||
#include <unordered_set>
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
struct ToConnect {
|
||||
std::string_view var_name;
|
||||
ProgramGraph::PortLocation dst;
|
||||
};
|
||||
|
||||
static uint32_t expand_node(ProgramGraph &graph, const ExpressionParser::Node &ep_node, const VoxelGraphNodeDB &db,
|
||||
std::vector<ToConnect> &to_connect, std::vector<uint32_t> &expanded_node_ids,
|
||||
Span<const ExpressionParser::Function> functions);
|
||||
|
||||
static bool expand_input(ProgramGraph &graph, const ExpressionParser::Node &arg, ProgramGraph::Node &pg_node,
|
||||
uint32_t pg_node_input_index, const VoxelGraphNodeDB &db, std::vector<ToConnect> &to_connect,
|
||||
std::vector<uint32_t> &expanded_node_ids, Span<const ExpressionParser::Function> functions) {
|
||||
switch (arg.type) {
|
||||
case ExpressionParser::Node::NUMBER: {
|
||||
const ExpressionParser::NumberNode &arg_nn = reinterpret_cast<const ExpressionParser::NumberNode &>(arg);
|
||||
pg_node.default_inputs[pg_node_input_index] = arg_nn.value;
|
||||
} break;
|
||||
|
||||
case ExpressionParser::Node::VARIABLE: {
|
||||
const ExpressionParser::VariableNode &arg_vn =
|
||||
reinterpret_cast<const ExpressionParser::VariableNode &>(arg);
|
||||
to_connect.push_back({ arg_vn.name, { pg_node.id, pg_node_input_index } });
|
||||
} break;
|
||||
|
||||
case ExpressionParser::Node::OPERATOR:
|
||||
case ExpressionParser::Node::FUNCTION: {
|
||||
const uint32_t dependency_pg_node_id =
|
||||
expand_node(graph, arg, db, to_connect, expanded_node_ids, functions);
|
||||
ERR_FAIL_COND_V(dependency_pg_node_id == ProgramGraph::NULL_ID, false);
|
||||
graph.connect({ dependency_pg_node_id, 0 }, { pg_node.id, pg_node_input_index });
|
||||
} break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static ProgramGraph::Node &create_node(
|
||||
ProgramGraph &graph, const VoxelGraphNodeDB &db, VoxelGeneratorGraph::NodeTypeID node_type_id) {
|
||||
ProgramGraph::Node *node = create_node_internal(graph, node_type_id, Vector2(), ProgramGraph::NULL_ID);
|
||||
CRASH_COND(node == nullptr);
|
||||
return *node;
|
||||
}
|
||||
|
||||
static uint32_t expand_node(ProgramGraph &graph, const ExpressionParser::Node &ep_node, const VoxelGraphNodeDB &db,
|
||||
std::vector<ToConnect> &to_connect, std::vector<uint32_t> &expanded_node_ids,
|
||||
Span<const ExpressionParser::Function> functions) {
|
||||
switch (ep_node.type) {
|
||||
case ExpressionParser::Node::NUMBER: {
|
||||
// Note, this code should only run if the whole expression is only a number.
|
||||
// Constant node inputs don't create a constant node, they just set the default value of the input.
|
||||
ProgramGraph::Node &pg_node = create_node(graph, db, VoxelGeneratorGraph::NODE_CONSTANT);
|
||||
const ExpressionParser::NumberNode &nn = reinterpret_cast<const ExpressionParser::NumberNode &>(ep_node);
|
||||
CRASH_COND(pg_node.params.size() != 1);
|
||||
pg_node.params[0] = nn.value;
|
||||
expanded_node_ids.push_back(pg_node.id);
|
||||
return pg_node.id;
|
||||
}
|
||||
|
||||
case ExpressionParser::Node::VARIABLE: {
|
||||
// Note, this code should only run if the whole expression is only a variable.
|
||||
// Variable node inputs don't create a node each time, they are turned into connections in a later pass.
|
||||
// Here we need a pass-through node, so let's use `var + 0`. It's not a common case anyways.
|
||||
ProgramGraph::Node &pg_node = create_node(graph, db, VoxelGeneratorGraph::NODE_ADD);
|
||||
const ExpressionParser::VariableNode &vn =
|
||||
reinterpret_cast<const ExpressionParser::VariableNode &>(ep_node);
|
||||
to_connect.push_back({ vn.name, { pg_node.id, 0 } });
|
||||
CRASH_COND(pg_node.default_inputs.size() != 2);
|
||||
pg_node.default_inputs[1] = 0;
|
||||
expanded_node_ids.push_back(pg_node.id);
|
||||
return pg_node.id;
|
||||
}
|
||||
|
||||
case ExpressionParser::Node::OPERATOR: {
|
||||
const ExpressionParser::OperatorNode &on =
|
||||
reinterpret_cast<const ExpressionParser::OperatorNode &>(ep_node);
|
||||
|
||||
CRASH_COND(on.n0 == nullptr);
|
||||
CRASH_COND(on.n1 == nullptr);
|
||||
|
||||
VoxelGeneratorGraph::NodeTypeID node_type_id;
|
||||
switch (on.op) {
|
||||
case ExpressionParser::OperatorNode::ADD:
|
||||
node_type_id = VoxelGeneratorGraph::NODE_ADD;
|
||||
break;
|
||||
case ExpressionParser::OperatorNode::SUBTRACT:
|
||||
node_type_id = VoxelGeneratorGraph::NODE_SUBTRACT;
|
||||
break;
|
||||
case ExpressionParser::OperatorNode::MULTIPLY:
|
||||
node_type_id = VoxelGeneratorGraph::NODE_MULTIPLY;
|
||||
break;
|
||||
case ExpressionParser::OperatorNode::DIVIDE:
|
||||
node_type_id = VoxelGeneratorGraph::NODE_DIVIDE;
|
||||
break;
|
||||
case ExpressionParser::OperatorNode::POWER:
|
||||
if (on.n1->type == ExpressionParser::Node::NUMBER) {
|
||||
// Attempt to use an optimized node if the power is constant
|
||||
const ExpressionParser::NumberNode &arg1 =
|
||||
static_cast<const ExpressionParser::NumberNode &>(*on.n1);
|
||||
|
||||
const int pi = int(arg1.value);
|
||||
if (Math::is_equal_approx(arg1.value, pi) && pi >= 0) {
|
||||
// Constant positive integer
|
||||
ProgramGraph::Node &pg_node = create_node(graph, db, VoxelGeneratorGraph::NODE_POWI);
|
||||
expanded_node_ids.push_back(pg_node.id);
|
||||
|
||||
CRASH_COND(pg_node.params.size() != 1);
|
||||
pg_node.params[0] = pi;
|
||||
|
||||
ERR_FAIL_COND_V(!expand_input(graph, *on.n0, pg_node, 0, db, to_connect, expanded_node_ids,
|
||||
functions),
|
||||
ProgramGraph::NULL_ID);
|
||||
|
||||
return pg_node.id;
|
||||
}
|
||||
}
|
||||
// Fallback on generic power function
|
||||
node_type_id = VoxelGeneratorGraph::NODE_POW;
|
||||
break;
|
||||
default:
|
||||
CRASH_NOW();
|
||||
break;
|
||||
}
|
||||
|
||||
ProgramGraph::Node &pg_node = create_node(graph, db, node_type_id);
|
||||
expanded_node_ids.push_back(pg_node.id);
|
||||
|
||||
ERR_FAIL_COND_V(!expand_input(graph, *on.n0, pg_node, 0, db, to_connect, expanded_node_ids, functions),
|
||||
ProgramGraph::NULL_ID);
|
||||
|
||||
ERR_FAIL_COND_V(!expand_input(graph, *on.n1, pg_node, 1, db, to_connect, expanded_node_ids, functions),
|
||||
ProgramGraph::NULL_ID);
|
||||
|
||||
return pg_node.id;
|
||||
}
|
||||
|
||||
case ExpressionParser::Node::FUNCTION: {
|
||||
const ExpressionParser::FunctionNode &fn =
|
||||
reinterpret_cast<const ExpressionParser::FunctionNode &>(ep_node);
|
||||
const ExpressionParser::Function *f = ExpressionParser::find_function_by_id(fn.function_id, functions);
|
||||
CRASH_COND(f == nullptr);
|
||||
const unsigned int arg_count = f->argument_count;
|
||||
|
||||
ProgramGraph::Node &pg_node = create_node(graph, db, VoxelGeneratorGraph::NodeTypeID(fn.function_id));
|
||||
// TODO Optimization: per-function shortcuts
|
||||
|
||||
for (unsigned int arg_index = 0; arg_index < arg_count; ++arg_index) {
|
||||
const ExpressionParser::Node *arg = fn.args[arg_index].get();
|
||||
CRASH_COND(arg == nullptr);
|
||||
ERR_FAIL_COND_V(
|
||||
!expand_input(graph, *arg, pg_node, arg_index, db, to_connect, expanded_node_ids, functions),
|
||||
ProgramGraph::NULL_ID);
|
||||
}
|
||||
|
||||
return pg_node.id;
|
||||
}
|
||||
|
||||
default:
|
||||
return ProgramGraph::NULL_ID;
|
||||
}
|
||||
}
|
||||
|
||||
static VoxelGraphRuntime::CompilationResult expand_expression_node(ProgramGraph &graph, uint32_t original_node_id,
|
||||
ProgramGraph::PortLocation &expanded_output_port, std::vector<uint32_t> &expanded_nodes,
|
||||
const VoxelGraphNodeDB &type_db) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
const ProgramGraph::Node &original_node = graph.get_node(original_node_id);
|
||||
CRASH_COND(original_node.params.size() == 0);
|
||||
const String code = original_node.params[0];
|
||||
const CharString code_utf8 = code.utf8();
|
||||
|
||||
Span<const ExpressionParser::Function> functions = type_db.get_expression_parser_functions();
|
||||
|
||||
// Extract the AST, so we can convert it into graph nodes,
|
||||
// and benefit from all features of range analysis and buffer processing
|
||||
ExpressionParser::Result parse_result = ExpressionParser::parse(code_utf8.get_data(), functions);
|
||||
|
||||
if (parse_result.error.id != ExpressionParser::ERROR_NONE) {
|
||||
// Error in expression
|
||||
const std::string error_message_utf8 = ExpressionParser::to_string(parse_result.error);
|
||||
VoxelGraphRuntime::CompilationResult result;
|
||||
result.success = false;
|
||||
result.node_id = original_node_id;
|
||||
result.message = String(error_message_utf8.c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
if (parse_result.root == nullptr) {
|
||||
// Expression is empty
|
||||
VoxelGraphRuntime::CompilationResult result;
|
||||
result.success = false;
|
||||
result.node_id = original_node_id;
|
||||
result.message = "Expression is empty";
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<ToConnect> to_connect;
|
||||
|
||||
// Create nodes from the expression's AST and connect them together
|
||||
const uint32_t expanded_root_node_id = expand_node(
|
||||
graph, *parse_result.root, VoxelGraphNodeDB::get_singleton(), to_connect, expanded_nodes, functions);
|
||||
if (expanded_root_node_id == ProgramGraph::NULL_ID) {
|
||||
VoxelGraphRuntime::CompilationResult result;
|
||||
result.success = false;
|
||||
result.node_id = original_node_id;
|
||||
result.message = "Internal error";
|
||||
return result;
|
||||
}
|
||||
|
||||
expanded_output_port = { expanded_root_node_id, 0 };
|
||||
|
||||
// Add connections from outside the expression to entry nodes of the expression
|
||||
for (const ToConnect tc : to_connect) {
|
||||
unsigned int original_port_index;
|
||||
if (!original_node.find_input_port_by_name(tc.var_name, original_port_index)) {
|
||||
VoxelGraphRuntime::CompilationResult result;
|
||||
result.success = false;
|
||||
result.node_id = original_node_id;
|
||||
result.message = "Could not resolve expression variable from input ports";
|
||||
return result;
|
||||
}
|
||||
const ProgramGraph::Port &original_port = original_node.inputs[original_port_index];
|
||||
for (unsigned int j = 0; j < original_port.connections.size(); ++j) {
|
||||
const ProgramGraph::PortLocation src = original_port.connections[j];
|
||||
graph.connect(src, tc.dst);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy first because we'll remove the original node
|
||||
CRASH_COND(original_node.outputs.size() == 0);
|
||||
const ProgramGraph::Port original_output_port_copy = original_node.outputs[0];
|
||||
|
||||
// Remove the original expression node
|
||||
graph.remove_node(original_node_id);
|
||||
|
||||
// Add connections from the expression's final node.
|
||||
// Must be done at the end because adding two connections to the same input (old and new) is not allowed.
|
||||
for (const ProgramGraph::PortLocation dst : original_output_port_copy.connections) {
|
||||
graph.connect(expanded_output_port, dst);
|
||||
}
|
||||
|
||||
VoxelGraphRuntime::CompilationResult result;
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
VoxelGraphRuntime::CompilationResult expand_expression_nodes(
|
||||
ProgramGraph &graph, const VoxelGraphNodeDB &type_db, GraphRemappingInfo *remap_info) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
// Gather expression node IDs first, as expansion could invalidate the iterator
|
||||
std::vector<uint32_t> expression_node_ids;
|
||||
graph.for_each_node([&expression_node_ids](ProgramGraph::Node &node) {
|
||||
if (node.type_id == VoxelGeneratorGraph::NODE_EXPRESSION) {
|
||||
expression_node_ids.push_back(node.id);
|
||||
}
|
||||
});
|
||||
|
||||
std::vector<uint32_t> expanded_node_ids;
|
||||
|
||||
for (const uint32_t node_id : expression_node_ids) {
|
||||
ProgramGraph::PortLocation expanded_output_port;
|
||||
expanded_node_ids.clear();
|
||||
const VoxelGraphRuntime::CompilationResult result =
|
||||
expand_expression_node(graph, node_id, expanded_output_port, expanded_node_ids, type_db);
|
||||
if (!result.success) {
|
||||
return result;
|
||||
}
|
||||
if (remap_info != nullptr) {
|
||||
remap_info->user_to_expanded_ports.push_back({ { node_id, 0 }, expanded_output_port });
|
||||
for (const uint32_t expanded_node_id : expanded_node_ids) {
|
||||
remap_info->expanded_to_user_node_ids.push_back({ expanded_node_id, node_id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VoxelGraphRuntime::CompilationResult result;
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::compile(const ProgramGraph &p_graph, bool debug) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
|
||||
const VoxelGraphNodeDB &type_db = VoxelGraphNodeDB::get_singleton();
|
||||
|
||||
ProgramGraph expanded_graph;
|
||||
expanded_graph.copy_from(p_graph, false);
|
||||
// TODO Store a remapping to allow debugging with the expanded graph
|
||||
GraphRemappingInfo remap_info;
|
||||
const VoxelGraphRuntime::CompilationResult expand_result =
|
||||
expand_expression_nodes(expanded_graph, type_db, &remap_info);
|
||||
if (!expand_result.success) {
|
||||
return expand_result;
|
||||
}
|
||||
// Expanding a graph may produce more nodes, not remove any
|
||||
ERR_FAIL_COND_V(expanded_graph.get_nodes_count() < p_graph.get_nodes_count(),
|
||||
CompilationResult::make_error("Internal error"));
|
||||
|
||||
const VoxelGraphRuntime::CompilationResult result = _compile(expanded_graph, debug, type_db);
|
||||
if (!result.success) {
|
||||
clear();
|
||||
}
|
||||
|
||||
for (PortRemap r : remap_info.user_to_expanded_ports) {
|
||||
_program.user_port_to_expanded_port.insert({ r.original, r.expanded });
|
||||
}
|
||||
for (ExpandedNodeRemap r : remap_info.expanded_to_user_node_ids) {
|
||||
_program.expanded_node_id_to_user_node_id.insert({ r.expanded_node_id, r.original_node_id });
|
||||
}
|
||||
// Remap debug nodes from the execution map to user-facing ones
|
||||
for (uint32_t &debug_node_id : _program.default_execution_map.debug_nodes) {
|
||||
auto it = _program.expanded_node_id_to_user_node_id.find(debug_node_id);
|
||||
if (it != _program.expanded_node_id_to_user_node_id.end()) {
|
||||
debug_node_id = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(
|
||||
const ProgramGraph &graph, bool debug, const VoxelGraphNodeDB &type_db) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
clear();
|
||||
|
||||
std::vector<uint32_t> order;
|
||||
std::vector<uint32_t> terminal_nodes;
|
||||
std::unordered_map<uint32_t, uint32_t> node_id_to_dependency_graph;
|
||||
|
||||
// Not using the generic `get_terminal_nodes` function because our terminal nodes do have outputs
|
||||
graph.for_each_node_const([&terminal_nodes, &type_db](const ProgramGraph::Node &node) {
|
||||
const VoxelGraphNodeDB::NodeType &type = type_db.get_type(node.type_id);
|
||||
if (type.category == VoxelGraphNodeDB::CATEGORY_OUTPUT) {
|
||||
terminal_nodes.push_back(node.id);
|
||||
}
|
||||
});
|
||||
|
||||
if (!debug) {
|
||||
// Exclude debug nodes
|
||||
unordered_remove_if(terminal_nodes, [&graph, &type_db](uint32_t node_id) {
|
||||
const ProgramGraph::Node &node = graph.get_node(node_id);
|
||||
const VoxelGraphNodeDB::NodeType &type = type_db.get_type(node.type_id);
|
||||
return type.debug_only;
|
||||
});
|
||||
}
|
||||
|
||||
graph.find_dependencies(terminal_nodes, order);
|
||||
|
||||
uint32_t xzy_start_index = 0;
|
||||
|
||||
// Optimize parts of the graph that only depend on X and Z,
|
||||
// so they can be moved in the outer loop when blocks are generated, running less times.
|
||||
// Moves them all at the beginning.
|
||||
{
|
||||
std::vector<uint32_t> immediate_deps;
|
||||
std::unordered_set<uint32_t> nodes_depending_on_y;
|
||||
std::vector<uint32_t> order_xz;
|
||||
std::vector<uint32_t> order_xzy;
|
||||
|
||||
for (size_t i = 0; i < order.size(); ++i) {
|
||||
const uint32_t node_id = order[i];
|
||||
const ProgramGraph::Node &node = graph.get_node(node_id);
|
||||
|
||||
bool depends_on_y = false;
|
||||
|
||||
if (node.type_id == VoxelGeneratorGraph::NODE_INPUT_Y) {
|
||||
nodes_depending_on_y.insert(node_id);
|
||||
depends_on_y = true;
|
||||
}
|
||||
|
||||
if (!depends_on_y) {
|
||||
immediate_deps.clear();
|
||||
graph.find_immediate_dependencies(node_id, immediate_deps);
|
||||
|
||||
for (size_t j = 0; j < immediate_deps.size(); ++j) {
|
||||
const uint32_t dep_node_id = immediate_deps[j];
|
||||
|
||||
if (nodes_depending_on_y.find(dep_node_id) != nodes_depending_on_y.end()) {
|
||||
depends_on_y = true;
|
||||
nodes_depending_on_y.insert(node_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (depends_on_y) {
|
||||
order_xzy.push_back(node_id);
|
||||
} else {
|
||||
order_xz.push_back(node_id);
|
||||
}
|
||||
}
|
||||
|
||||
xzy_start_index = order_xz.size();
|
||||
|
||||
//#ifdef DEBUG_ENABLED
|
||||
// const uint32_t order_xz_raw_size = order_xz.size();
|
||||
// const uint32_t *order_xz_raw = order_xz.data();
|
||||
// const uint32_t order_xzy_raw_size = order_xzy.size();
|
||||
// const uint32_t *order_xzy_raw = order_xzy.data();
|
||||
//#endif
|
||||
|
||||
size_t i = 0;
|
||||
for (size_t j = 0; j < order_xz.size(); ++j) {
|
||||
order[i++] = order_xz[j];
|
||||
}
|
||||
for (size_t j = 0; j < order_xzy.size(); ++j) {
|
||||
order[i++] = order_xzy[j];
|
||||
}
|
||||
}
|
||||
|
||||
//#ifdef DEBUG_ENABLED
|
||||
// const uint32_t order_raw_size = order.size();
|
||||
// const uint32_t *order_raw = order.data();
|
||||
//#endif
|
||||
|
||||
struct MemoryHelper {
|
||||
std::vector<BufferSpec> &buffer_specs;
|
||||
unsigned int next_address = 0;
|
||||
|
||||
uint16_t add_binding() {
|
||||
const unsigned int a = next_address;
|
||||
++next_address;
|
||||
BufferSpec bs;
|
||||
bs.address = a;
|
||||
bs.is_binding = true;
|
||||
bs.is_constant = false;
|
||||
bs.users_count = 0;
|
||||
buffer_specs.push_back(bs);
|
||||
return a;
|
||||
}
|
||||
|
||||
uint16_t add_var() {
|
||||
const unsigned int a = next_address;
|
||||
++next_address;
|
||||
BufferSpec bs;
|
||||
bs.address = a;
|
||||
bs.is_binding = false;
|
||||
bs.is_constant = false;
|
||||
bs.users_count = 0;
|
||||
buffer_specs.push_back(bs);
|
||||
return a;
|
||||
}
|
||||
|
||||
uint16_t add_constant(float v) {
|
||||
const unsigned int a = next_address;
|
||||
++next_address;
|
||||
BufferSpec bs;
|
||||
bs.address = a;
|
||||
bs.constant_value = v;
|
||||
bs.is_binding = false;
|
||||
bs.is_constant = true;
|
||||
bs.users_count = 0;
|
||||
buffer_specs.push_back(bs);
|
||||
return a;
|
||||
}
|
||||
};
|
||||
|
||||
MemoryHelper mem{ _program.buffer_specs };
|
||||
|
||||
// Main inputs X, Y, Z
|
||||
_program.x_input_address = mem.add_binding();
|
||||
_program.y_input_address = mem.add_binding();
|
||||
_program.z_input_address = mem.add_binding();
|
||||
|
||||
std::vector<uint16_t> &operations = _program.operations;
|
||||
|
||||
// Run through each node in order, and turn them into program instructions
|
||||
for (size_t order_index = 0; order_index < order.size(); ++order_index) {
|
||||
const uint32_t node_id = order[order_index];
|
||||
const ProgramGraph::Node &node = graph.get_node(node_id);
|
||||
const VoxelGraphNodeDB::NodeType &type = type_db.get_type(node.type_id);
|
||||
|
||||
CRASH_COND(node.inputs.size() != type.inputs.size());
|
||||
CRASH_COND(node.outputs.size() != type.outputs.size());
|
||||
|
||||
if (order_index == xzy_start_index) {
|
||||
_program.xzy_start_op_address = operations.size();
|
||||
}
|
||||
|
||||
const unsigned int dg_node_index = _program.dependency_graph.nodes.size();
|
||||
_program.dependency_graph.nodes.push_back(DependencyGraph::Node());
|
||||
DependencyGraph::Node &dg_node = _program.dependency_graph.nodes.back();
|
||||
dg_node.is_input = false;
|
||||
dg_node.op_address = operations.size();
|
||||
dg_node.first_dependency = _program.dependency_graph.dependencies.size();
|
||||
dg_node.end_dependency = dg_node.first_dependency;
|
||||
dg_node.debug_node_id = node_id;
|
||||
node_id_to_dependency_graph.insert(std::make_pair(node_id, dg_node_index));
|
||||
|
||||
// We still hardcode some of the nodes. Maybe we can abstract them too one day.
|
||||
switch (node.type_id) {
|
||||
case VoxelGeneratorGraph::NODE_CONSTANT: {
|
||||
CRASH_COND(type.outputs.size() != 1);
|
||||
CRASH_COND(type.params.size() != 1);
|
||||
const uint16_t a = mem.add_constant(node.params[0].operator float());
|
||||
_program.output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = a;
|
||||
// Technically not an input or an output, but is a dependency regardless so treat it like an input
|
||||
dg_node.is_input = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Input nodes can appear multiple times in the graph, for convenience.
|
||||
// Multiple instances of the same node will refer to the same data.
|
||||
case VoxelGeneratorGraph::NODE_INPUT_X:
|
||||
_program.output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = _program.x_input_address;
|
||||
dg_node.is_input = true;
|
||||
continue;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_INPUT_Y:
|
||||
_program.output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = _program.y_input_address;
|
||||
dg_node.is_input = true;
|
||||
continue;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_INPUT_Z:
|
||||
_program.output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = _program.z_input_address;
|
||||
dg_node.is_input = true;
|
||||
continue;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_SDF_PREVIEW:
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add actual operation
|
||||
|
||||
CRASH_COND(node.type_id > 0xff);
|
||||
|
||||
if (order_index == xzy_start_index) {
|
||||
_program.default_execution_map.xzy_start_index = _program.default_execution_map.operation_adresses.size();
|
||||
}
|
||||
_program.default_execution_map.operation_adresses.push_back(operations.size());
|
||||
if (debug) {
|
||||
// Will be remapped later if the node is an expanded one
|
||||
_program.default_execution_map.debug_nodes.push_back(node_id);
|
||||
}
|
||||
|
||||
operations.push_back(node.type_id);
|
||||
|
||||
// Inputs and outputs use a convention so we can have generic code for them.
|
||||
// Parameters are more specific, and may be affected by alignment so better just do them by hand
|
||||
|
||||
// Add inputs
|
||||
for (size_t j = 0; j < type.inputs.size(); ++j) {
|
||||
uint16_t a;
|
||||
|
||||
if (node.inputs[j].connections.size() == 0) {
|
||||
// No input, default it
|
||||
CRASH_COND(j >= node.default_inputs.size());
|
||||
float defval = node.default_inputs[j];
|
||||
a = mem.add_constant(defval);
|
||||
|
||||
} else {
|
||||
ProgramGraph::PortLocation src_port = node.inputs[j].connections[0];
|
||||
const uint16_t *aptr = _program.output_port_addresses.getptr(src_port);
|
||||
// Previous node ports must have been registered
|
||||
CRASH_COND(aptr == nullptr);
|
||||
a = *aptr;
|
||||
|
||||
// Register dependency
|
||||
auto it = node_id_to_dependency_graph.find(src_port.node_id);
|
||||
CRASH_COND(it == node_id_to_dependency_graph.end());
|
||||
CRASH_COND(it->second >= _program.dependency_graph.nodes.size());
|
||||
_program.dependency_graph.dependencies.push_back(it->second);
|
||||
++dg_node.end_dependency;
|
||||
}
|
||||
|
||||
operations.push_back(a);
|
||||
|
||||
BufferSpec &bs = _program.buffer_specs[a];
|
||||
++bs.users_count;
|
||||
}
|
||||
|
||||
// Add outputs
|
||||
for (size_t j = 0; j < type.outputs.size(); ++j) {
|
||||
const uint16_t a = mem.add_var();
|
||||
|
||||
// This will be used by next nodes
|
||||
const ProgramGraph::PortLocation op{ node_id, static_cast<uint32_t>(j) };
|
||||
_program.output_port_addresses[op] = a;
|
||||
|
||||
operations.push_back(a);
|
||||
}
|
||||
|
||||
// Add space for params size, default is no params so size is 0
|
||||
size_t params_size_index = operations.size();
|
||||
operations.push_back(0);
|
||||
|
||||
// Get params, copy resources when used, and hold a reference to them
|
||||
std::vector<Variant> params_copy;
|
||||
params_copy.resize(node.params.size());
|
||||
for (size_t i = 0; i < node.params.size(); ++i) {
|
||||
Variant v = node.params[i];
|
||||
|
||||
if (v.get_type() == Variant::OBJECT) {
|
||||
Ref<Resource> res = v;
|
||||
|
||||
if (res.is_null()) {
|
||||
// duplicate() is only available in Resource,
|
||||
// so we have to limit to this instead of Reference or Object
|
||||
CompilationResult result;
|
||||
result.success = false;
|
||||
result.message = ZN_TTR("A parameter is an object but does not inherit Resource");
|
||||
result.node_id = node_id;
|
||||
return result;
|
||||
}
|
||||
|
||||
res = res->duplicate();
|
||||
|
||||
_program.ref_resources.push_back(res);
|
||||
v = res;
|
||||
}
|
||||
|
||||
params_copy[i] = v;
|
||||
}
|
||||
|
||||
if (type.compile_func != nullptr) {
|
||||
CompileContext ctx(/**node,*/ operations, _program.heap_resources, params_copy);
|
||||
type.compile_func(ctx);
|
||||
if (ctx.has_error()) {
|
||||
CompilationResult result;
|
||||
result.success = false;
|
||||
result.message = ctx.get_error_message();
|
||||
result.node_id = node_id;
|
||||
return result;
|
||||
}
|
||||
const size_t params_size = ctx.get_params_size_in_words();
|
||||
CRASH_COND(params_size > std::numeric_limits<uint16_t>::max());
|
||||
operations[params_size_index] = params_size;
|
||||
}
|
||||
|
||||
if (type.category == VoxelGraphNodeDB::CATEGORY_OUTPUT) {
|
||||
CRASH_COND(node.outputs.size() != 1);
|
||||
|
||||
if (_program.outputs_count == _program.outputs.size()) {
|
||||
CompilationResult result;
|
||||
result.success = false;
|
||||
result.message = ZN_TTR("Maximum number of outputs has been reached");
|
||||
result.node_id = node_id;
|
||||
return result;
|
||||
}
|
||||
|
||||
{
|
||||
const uint16_t *aptr = _program.output_port_addresses.getptr(ProgramGraph::PortLocation{ node_id, 0 });
|
||||
// Previous node ports must have been registered
|
||||
CRASH_COND(aptr == nullptr);
|
||||
OutputInfo &output_info = _program.outputs[_program.outputs_count];
|
||||
output_info.buffer_address = *aptr;
|
||||
output_info.dependency_graph_node_index = dg_node_index;
|
||||
output_info.node_id = node_id;
|
||||
++_program.outputs_count;
|
||||
}
|
||||
|
||||
// Add fake user for output ports so they can pass the local users check in optimizations
|
||||
for (unsigned int j = 0; j < type.outputs.size(); ++j) {
|
||||
const ProgramGraph::PortLocation loc{ node_id, j };
|
||||
const uint16_t *aptr = _program.output_port_addresses.getptr(loc);
|
||||
CRASH_COND(aptr == nullptr);
|
||||
BufferSpec &bs = _program.buffer_specs[*aptr];
|
||||
// Not expecting existing users on that port
|
||||
ERR_FAIL_COND_V(bs.users_count != 0, CompilationResult());
|
||||
++bs.users_count;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef VOXEL_DEBUG_GRAPH_PROG_SENTINEL
|
||||
// Append a special value after each operation
|
||||
append(operations, VOXEL_DEBUG_GRAPH_PROG_SENTINEL);
|
||||
#endif
|
||||
}
|
||||
|
||||
_program.buffer_count = mem.next_address;
|
||||
|
||||
ZN_PRINT_VERBOSE(format("Compiled voxel graph. Program size: {}b, buffers: {}",
|
||||
_program.operations.size() * sizeof(uint16_t), _program.buffer_count));
|
||||
|
||||
CompilationResult result;
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace zylann::voxel
|
116
generators/graph/voxel_graph_compiler.h
Normal file
116
generators/graph/voxel_graph_compiler.h
Normal file
@ -0,0 +1,116 @@
|
||||
#ifndef VOXEL_GRAPH_COMPILER_H
|
||||
#define VOXEL_GRAPH_COMPILER_H
|
||||
|
||||
#include "voxel_graph_runtime.h"
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
struct PortRemap {
|
||||
ProgramGraph::PortLocation original;
|
||||
ProgramGraph::PortLocation expanded;
|
||||
};
|
||||
|
||||
struct ExpandedNodeRemap {
|
||||
uint32_t expanded_node_id;
|
||||
uint32_t original_node_id;
|
||||
};
|
||||
|
||||
struct GraphRemappingInfo {
|
||||
std::vector<PortRemap> user_to_expanded_ports;
|
||||
std::vector<ExpandedNodeRemap> expanded_to_user_node_ids;
|
||||
};
|
||||
|
||||
VoxelGraphRuntime::CompilationResult expand_expression_nodes(
|
||||
ProgramGraph &graph, const VoxelGraphNodeDB &type_db, GraphRemappingInfo *remap_info);
|
||||
|
||||
// Functions usable by node implementations during the compilation stage
|
||||
class CompileContext {
|
||||
public:
|
||||
CompileContext(/*const ProgramGraph::Node &node,*/ std::vector<uint16_t> &program,
|
||||
std::vector<VoxelGraphRuntime::HeapResource> &heap_resources, std::vector<Variant> ¶ms) :
|
||||
/*_node(node),*/ _program(program), _heap_resources(heap_resources), _params(params) {}
|
||||
|
||||
Variant get_param(size_t i) const {
|
||||
CRASH_COND(i > _params.size());
|
||||
return _params[i];
|
||||
}
|
||||
|
||||
// Typical use is to pass a struct containing all compile-time arguments the operation will need
|
||||
template <typename T>
|
||||
void set_params(T params) {
|
||||
// Can be called only once per node
|
||||
CRASH_COND(_params_added);
|
||||
// We will need to align memory, so the struct will not be immediately stored here.
|
||||
// Instead we put a header that tells how much to advance in order to reach the beginning of the struct,
|
||||
// which will be at an aligned position.
|
||||
// We align to the maximum alignment between the struct,
|
||||
// and the type of word we store inside the program buffer, which is uint16.
|
||||
const size_t params_alignment = math::max(alignof(T), alignof(uint16_t));
|
||||
const size_t params_offset_index = _program.size();
|
||||
// Prepare space to store the offset (at least 1 since that header is one word)
|
||||
_program.push_back(1);
|
||||
// Align memory for the struct.
|
||||
// Note, we index with words, not bytes.
|
||||
const size_t struct_offset =
|
||||
math::alignup(_program.size() * sizeof(uint16_t), params_alignment) / sizeof(uint16_t);
|
||||
if (struct_offset > _program.size()) {
|
||||
_program.resize(struct_offset);
|
||||
}
|
||||
// Write offset in header
|
||||
_program[params_offset_index] = struct_offset - params_offset_index;
|
||||
// Allocate space for the struct. It is measured in words, so it can be up to 1 byte larger.
|
||||
_params_size_in_words = (sizeof(T) + sizeof(uint16_t) - 1) / sizeof(uint16_t);
|
||||
_program.resize(_program.size() + _params_size_in_words);
|
||||
// Write struct
|
||||
T &p = *reinterpret_cast<T *>(&_program[struct_offset]);
|
||||
p = params;
|
||||
|
||||
_params_added = true;
|
||||
}
|
||||
|
||||
// In case the compilation step produces a resource to be deleted
|
||||
template <typename T>
|
||||
void add_memdelete_cleanup(T *ptr) {
|
||||
VoxelGraphRuntime::HeapResource hr;
|
||||
hr.ptr = ptr;
|
||||
hr.deleter = [](void *p) {
|
||||
// TODO We have no guarantee it was allocated with memnew :|
|
||||
T *tp = reinterpret_cast<T *>(p);
|
||||
memdelete(tp);
|
||||
};
|
||||
_heap_resources.push_back(hr);
|
||||
}
|
||||
|
||||
void make_error(String message) {
|
||||
_error_message = message;
|
||||
_has_error = true;
|
||||
}
|
||||
|
||||
bool has_error() const {
|
||||
return _has_error;
|
||||
}
|
||||
|
||||
const String &get_error_message() const {
|
||||
return _error_message;
|
||||
}
|
||||
|
||||
size_t get_params_size_in_words() const {
|
||||
return _params_size_in_words;
|
||||
}
|
||||
|
||||
private:
|
||||
//const ProgramGraph::Node &_node;
|
||||
std::vector<uint16_t> &_program;
|
||||
std::vector<VoxelGraphRuntime::HeapResource> &_heap_resources;
|
||||
std::vector<Variant> &_params;
|
||||
String _error_message;
|
||||
size_t _params_size_in_words = 0;
|
||||
bool _has_error = false;
|
||||
bool _params_added = false;
|
||||
};
|
||||
|
||||
typedef void (*CompileFunc)(CompileContext &);
|
||||
|
||||
} // namespace zylann::voxel
|
||||
|
||||
#endif // VOXEL_GRAPH_COMPILER_H
|
@ -255,10 +255,10 @@ const char *VoxelGraphNodeDB::get_category_name(Category category) {
|
||||
}
|
||||
|
||||
VoxelGraphNodeDB::VoxelGraphNodeDB() {
|
||||
typedef VoxelGraphRuntime::CompileContext CompileContext;
|
||||
//typedef VoxelGraphRuntime::CompileContext CompileContext;
|
||||
typedef VoxelGraphRuntime::ProcessBufferContext ProcessBufferContext;
|
||||
typedef VoxelGraphRuntime::RangeAnalysisContext RangeAnalysisContext;
|
||||
typedef VoxelGraphRuntime::ShaderGenContext ShaderGenContext;
|
||||
//typedef VoxelGraphRuntime::ShaderGenContext ShaderGenContext;
|
||||
|
||||
FixedArray<NodeType, VoxelGeneratorGraph::NODE_TYPE_COUNT> &types = _types;
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#include "../../util/expression_parser.h"
|
||||
#include "voxel_generator_graph.h"
|
||||
#include "voxel_graph_compiler.h"
|
||||
#include "voxel_graph_shader_generator.h"
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
@ -57,12 +59,12 @@ public:
|
||||
std::vector<Param> params;
|
||||
HashMap<String, uint32_t> param_name_to_index;
|
||||
HashMap<String, uint32_t> input_name_to_index;
|
||||
VoxelGraphRuntime::CompileFunc compile_func = nullptr;
|
||||
CompileFunc compile_func = nullptr;
|
||||
VoxelGraphRuntime::ProcessBufferFunc process_buffer_func = nullptr;
|
||||
VoxelGraphRuntime::RangeAnalysisFunc range_analysis_func = nullptr;
|
||||
const char *expression_func_name = nullptr;
|
||||
ExpressionParser::FunctionCallback expression_func = nullptr;
|
||||
VoxelGraphRuntime::ShaderGenFunc shader_gen_func = nullptr;
|
||||
ShaderGenFunc shader_gen_func = nullptr;
|
||||
};
|
||||
|
||||
VoxelGraphNodeDB();
|
||||
|
@ -1,6 +1,5 @@
|
||||
#include "voxel_graph_runtime.h"
|
||||
#include "../../util/container_funcs.h"
|
||||
#include "../../util/expression_parser.h"
|
||||
#include "../../util/log.h"
|
||||
#include "../../util/macros.h"
|
||||
#include "../../util/profiling.h"
|
||||
@ -31,706 +30,6 @@ void VoxelGraphRuntime::clear() {
|
||||
_program.clear();
|
||||
}
|
||||
|
||||
struct ToConnect {
|
||||
std::string_view var_name;
|
||||
ProgramGraph::PortLocation dst;
|
||||
};
|
||||
|
||||
static uint32_t expand_node(ProgramGraph &graph, const ExpressionParser::Node &ep_node, const VoxelGraphNodeDB &db,
|
||||
std::vector<ToConnect> &to_connect, std::vector<uint32_t> &expanded_node_ids,
|
||||
Span<const ExpressionParser::Function> functions);
|
||||
|
||||
static bool expand_input(ProgramGraph &graph, const ExpressionParser::Node &arg, ProgramGraph::Node &pg_node,
|
||||
uint32_t pg_node_input_index, const VoxelGraphNodeDB &db, std::vector<ToConnect> &to_connect,
|
||||
std::vector<uint32_t> &expanded_node_ids, Span<const ExpressionParser::Function> functions) {
|
||||
switch (arg.type) {
|
||||
case ExpressionParser::Node::NUMBER: {
|
||||
const ExpressionParser::NumberNode &arg_nn = reinterpret_cast<const ExpressionParser::NumberNode &>(arg);
|
||||
pg_node.default_inputs[pg_node_input_index] = arg_nn.value;
|
||||
} break;
|
||||
|
||||
case ExpressionParser::Node::VARIABLE: {
|
||||
const ExpressionParser::VariableNode &arg_vn =
|
||||
reinterpret_cast<const ExpressionParser::VariableNode &>(arg);
|
||||
to_connect.push_back({ arg_vn.name, { pg_node.id, pg_node_input_index } });
|
||||
} break;
|
||||
|
||||
case ExpressionParser::Node::OPERATOR:
|
||||
case ExpressionParser::Node::FUNCTION: {
|
||||
const uint32_t dependency_pg_node_id =
|
||||
expand_node(graph, arg, db, to_connect, expanded_node_ids, functions);
|
||||
ERR_FAIL_COND_V(dependency_pg_node_id == ProgramGraph::NULL_ID, false);
|
||||
graph.connect({ dependency_pg_node_id, 0 }, { pg_node.id, pg_node_input_index });
|
||||
} break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static ProgramGraph::Node &create_node(
|
||||
ProgramGraph &graph, const VoxelGraphNodeDB &db, VoxelGeneratorGraph::NodeTypeID node_type_id) {
|
||||
ProgramGraph::Node *node = create_node_internal(graph, node_type_id, Vector2(), ProgramGraph::NULL_ID);
|
||||
CRASH_COND(node == nullptr);
|
||||
return *node;
|
||||
}
|
||||
|
||||
static uint32_t expand_node(ProgramGraph &graph, const ExpressionParser::Node &ep_node, const VoxelGraphNodeDB &db,
|
||||
std::vector<ToConnect> &to_connect, std::vector<uint32_t> &expanded_node_ids,
|
||||
Span<const ExpressionParser::Function> functions) {
|
||||
switch (ep_node.type) {
|
||||
case ExpressionParser::Node::NUMBER: {
|
||||
// Note, this code should only run if the whole expression is only a number.
|
||||
// Constant node inputs don't create a constant node, they just set the default value of the input.
|
||||
ProgramGraph::Node &pg_node = create_node(graph, db, VoxelGeneratorGraph::NODE_CONSTANT);
|
||||
const ExpressionParser::NumberNode &nn = reinterpret_cast<const ExpressionParser::NumberNode &>(ep_node);
|
||||
CRASH_COND(pg_node.params.size() != 1);
|
||||
pg_node.params[0] = nn.value;
|
||||
expanded_node_ids.push_back(pg_node.id);
|
||||
return pg_node.id;
|
||||
}
|
||||
|
||||
case ExpressionParser::Node::VARIABLE: {
|
||||
// Note, this code should only run if the whole expression is only a variable.
|
||||
// Variable node inputs don't create a node each time, they are turned into connections in a later pass.
|
||||
// Here we need a pass-through node, so let's use `var + 0`. It's not a common case anyways.
|
||||
ProgramGraph::Node &pg_node = create_node(graph, db, VoxelGeneratorGraph::NODE_ADD);
|
||||
const ExpressionParser::VariableNode &vn =
|
||||
reinterpret_cast<const ExpressionParser::VariableNode &>(ep_node);
|
||||
to_connect.push_back({ vn.name, { pg_node.id, 0 } });
|
||||
CRASH_COND(pg_node.default_inputs.size() != 2);
|
||||
pg_node.default_inputs[1] = 0;
|
||||
expanded_node_ids.push_back(pg_node.id);
|
||||
return pg_node.id;
|
||||
}
|
||||
|
||||
case ExpressionParser::Node::OPERATOR: {
|
||||
const ExpressionParser::OperatorNode &on =
|
||||
reinterpret_cast<const ExpressionParser::OperatorNode &>(ep_node);
|
||||
|
||||
CRASH_COND(on.n0 == nullptr);
|
||||
CRASH_COND(on.n1 == nullptr);
|
||||
|
||||
VoxelGeneratorGraph::NodeTypeID node_type_id;
|
||||
switch (on.op) {
|
||||
case ExpressionParser::OperatorNode::ADD:
|
||||
node_type_id = VoxelGeneratorGraph::NODE_ADD;
|
||||
break;
|
||||
case ExpressionParser::OperatorNode::SUBTRACT:
|
||||
node_type_id = VoxelGeneratorGraph::NODE_SUBTRACT;
|
||||
break;
|
||||
case ExpressionParser::OperatorNode::MULTIPLY:
|
||||
node_type_id = VoxelGeneratorGraph::NODE_MULTIPLY;
|
||||
break;
|
||||
case ExpressionParser::OperatorNode::DIVIDE:
|
||||
node_type_id = VoxelGeneratorGraph::NODE_DIVIDE;
|
||||
break;
|
||||
case ExpressionParser::OperatorNode::POWER:
|
||||
if (on.n1->type == ExpressionParser::Node::NUMBER) {
|
||||
// Attempt to use an optimized node if the power is constant
|
||||
const ExpressionParser::NumberNode &arg1 =
|
||||
static_cast<const ExpressionParser::NumberNode &>(*on.n1);
|
||||
|
||||
const int pi = int(arg1.value);
|
||||
if (Math::is_equal_approx(arg1.value, pi) && pi >= 0) {
|
||||
// Constant positive integer
|
||||
ProgramGraph::Node &pg_node = create_node(graph, db, VoxelGeneratorGraph::NODE_POWI);
|
||||
expanded_node_ids.push_back(pg_node.id);
|
||||
|
||||
CRASH_COND(pg_node.params.size() != 1);
|
||||
pg_node.params[0] = pi;
|
||||
|
||||
ERR_FAIL_COND_V(!expand_input(graph, *on.n0, pg_node, 0, db, to_connect, expanded_node_ids,
|
||||
functions),
|
||||
ProgramGraph::NULL_ID);
|
||||
|
||||
return pg_node.id;
|
||||
}
|
||||
}
|
||||
// Fallback on generic power function
|
||||
node_type_id = VoxelGeneratorGraph::NODE_POW;
|
||||
break;
|
||||
default:
|
||||
CRASH_NOW();
|
||||
break;
|
||||
}
|
||||
|
||||
ProgramGraph::Node &pg_node = create_node(graph, db, node_type_id);
|
||||
expanded_node_ids.push_back(pg_node.id);
|
||||
|
||||
ERR_FAIL_COND_V(!expand_input(graph, *on.n0, pg_node, 0, db, to_connect, expanded_node_ids, functions),
|
||||
ProgramGraph::NULL_ID);
|
||||
|
||||
ERR_FAIL_COND_V(!expand_input(graph, *on.n1, pg_node, 1, db, to_connect, expanded_node_ids, functions),
|
||||
ProgramGraph::NULL_ID);
|
||||
|
||||
return pg_node.id;
|
||||
}
|
||||
|
||||
case ExpressionParser::Node::FUNCTION: {
|
||||
const ExpressionParser::FunctionNode &fn =
|
||||
reinterpret_cast<const ExpressionParser::FunctionNode &>(ep_node);
|
||||
const ExpressionParser::Function *f = ExpressionParser::find_function_by_id(fn.function_id, functions);
|
||||
CRASH_COND(f == nullptr);
|
||||
const unsigned int arg_count = f->argument_count;
|
||||
|
||||
ProgramGraph::Node &pg_node = create_node(graph, db, VoxelGeneratorGraph::NodeTypeID(fn.function_id));
|
||||
// TODO Optimization: per-function shortcuts
|
||||
|
||||
for (unsigned int arg_index = 0; arg_index < arg_count; ++arg_index) {
|
||||
const ExpressionParser::Node *arg = fn.args[arg_index].get();
|
||||
CRASH_COND(arg == nullptr);
|
||||
ERR_FAIL_COND_V(
|
||||
!expand_input(graph, *arg, pg_node, arg_index, db, to_connect, expanded_node_ids, functions),
|
||||
ProgramGraph::NULL_ID);
|
||||
}
|
||||
|
||||
return pg_node.id;
|
||||
}
|
||||
|
||||
default:
|
||||
return ProgramGraph::NULL_ID;
|
||||
}
|
||||
}
|
||||
|
||||
static VoxelGraphRuntime::CompilationResult expand_expression_node(ProgramGraph &graph, uint32_t original_node_id,
|
||||
ProgramGraph::PortLocation &expanded_output_port, std::vector<uint32_t> &expanded_nodes) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
const ProgramGraph::Node &original_node = graph.get_node(original_node_id);
|
||||
CRASH_COND(original_node.params.size() == 0);
|
||||
const String code = original_node.params[0];
|
||||
const CharString code_utf8 = code.utf8();
|
||||
|
||||
Span<const ExpressionParser::Function> functions =
|
||||
VoxelGraphNodeDB::get_singleton().get_expression_parser_functions();
|
||||
|
||||
// Extract the AST, so we can convert it into graph nodes,
|
||||
// and benefit from all features of range analysis and buffer processing
|
||||
ExpressionParser::Result parse_result = ExpressionParser::parse(code_utf8.get_data(), functions);
|
||||
|
||||
if (parse_result.error.id != ExpressionParser::ERROR_NONE) {
|
||||
// Error in expression
|
||||
const std::string error_message_utf8 = ExpressionParser::to_string(parse_result.error);
|
||||
VoxelGraphRuntime::CompilationResult result;
|
||||
result.success = false;
|
||||
result.node_id = original_node_id;
|
||||
result.message = String(error_message_utf8.c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
if (parse_result.root == nullptr) {
|
||||
// Expression is empty
|
||||
VoxelGraphRuntime::CompilationResult result;
|
||||
result.success = false;
|
||||
result.node_id = original_node_id;
|
||||
result.message = "Expression is empty";
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<ToConnect> to_connect;
|
||||
|
||||
// Create nodes from the expression's AST and connect them together
|
||||
const uint32_t expanded_root_node_id = expand_node(
|
||||
graph, *parse_result.root, VoxelGraphNodeDB::get_singleton(), to_connect, expanded_nodes, functions);
|
||||
if (expanded_root_node_id == ProgramGraph::NULL_ID) {
|
||||
VoxelGraphRuntime::CompilationResult result;
|
||||
result.success = false;
|
||||
result.node_id = original_node_id;
|
||||
result.message = "Internal error";
|
||||
return result;
|
||||
}
|
||||
|
||||
expanded_output_port = { expanded_root_node_id, 0 };
|
||||
|
||||
// Add connections from outside the expression to entry nodes of the expression
|
||||
for (const ToConnect tc : to_connect) {
|
||||
unsigned int original_port_index;
|
||||
if (!original_node.find_input_port_by_name(tc.var_name, original_port_index)) {
|
||||
VoxelGraphRuntime::CompilationResult result;
|
||||
result.success = false;
|
||||
result.node_id = original_node_id;
|
||||
result.message = "Could not resolve expression variable from input ports";
|
||||
return result;
|
||||
}
|
||||
const ProgramGraph::Port &original_port = original_node.inputs[original_port_index];
|
||||
for (unsigned int j = 0; j < original_port.connections.size(); ++j) {
|
||||
const ProgramGraph::PortLocation src = original_port.connections[j];
|
||||
graph.connect(src, tc.dst);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy first because we'll remove the original node
|
||||
CRASH_COND(original_node.outputs.size() == 0);
|
||||
const ProgramGraph::Port original_output_port_copy = original_node.outputs[0];
|
||||
|
||||
// Remove the original expression node
|
||||
graph.remove_node(original_node_id);
|
||||
|
||||
// Add connections from the expression's final node.
|
||||
// Must be done at the end because adding two connections to the same input (old and new) is not allowed.
|
||||
for (const ProgramGraph::PortLocation dst : original_output_port_copy.connections) {
|
||||
graph.connect(expanded_output_port, dst);
|
||||
}
|
||||
|
||||
VoxelGraphRuntime::CompilationResult result;
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
struct PortRemap {
|
||||
ProgramGraph::PortLocation original;
|
||||
ProgramGraph::PortLocation expanded;
|
||||
};
|
||||
|
||||
struct ExpandedNodeRemap {
|
||||
uint32_t expanded_node_id;
|
||||
uint32_t original_node_id;
|
||||
};
|
||||
|
||||
struct GraphRemappingInfo {
|
||||
std::vector<PortRemap> user_to_expanded_ports;
|
||||
std::vector<ExpandedNodeRemap> expanded_to_user_node_ids;
|
||||
};
|
||||
|
||||
static VoxelGraphRuntime::CompilationResult expand_expression_nodes(
|
||||
ProgramGraph &graph, GraphRemappingInfo *remap_info) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
// Gather expression node IDs first, as expansion could invalidate the iterator
|
||||
std::vector<uint32_t> expression_node_ids;
|
||||
graph.for_each_node([&expression_node_ids](ProgramGraph::Node &node) {
|
||||
if (node.type_id == VoxelGeneratorGraph::NODE_EXPRESSION) {
|
||||
expression_node_ids.push_back(node.id);
|
||||
}
|
||||
});
|
||||
|
||||
std::vector<uint32_t> expanded_node_ids;
|
||||
|
||||
for (const uint32_t node_id : expression_node_ids) {
|
||||
ProgramGraph::PortLocation expanded_output_port;
|
||||
expanded_node_ids.clear();
|
||||
const VoxelGraphRuntime::CompilationResult result =
|
||||
expand_expression_node(graph, node_id, expanded_output_port, expanded_node_ids);
|
||||
if (!result.success) {
|
||||
return result;
|
||||
}
|
||||
if (remap_info != nullptr) {
|
||||
remap_info->user_to_expanded_ports.push_back({ { node_id, 0 }, expanded_output_port });
|
||||
for (const uint32_t expanded_node_id : expanded_node_ids) {
|
||||
remap_info->expanded_to_user_node_ids.push_back({ expanded_node_id, node_id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VoxelGraphRuntime::CompilationResult result;
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
static VoxelGraphRuntime::CompilationResult make_error(const char *p_message, int p_node_id = -1) {
|
||||
VoxelGraphRuntime::CompilationResult res;
|
||||
res.success = false;
|
||||
res.node_id = p_node_id;
|
||||
res.message = p_message;
|
||||
return res;
|
||||
}
|
||||
|
||||
VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::compile(const ProgramGraph &p_graph, bool debug) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
|
||||
ProgramGraph expanded_graph;
|
||||
expanded_graph.copy_from(p_graph, false);
|
||||
// TODO Store a remapping to allow debugging with the expanded graph
|
||||
GraphRemappingInfo remap_info;
|
||||
const VoxelGraphRuntime::CompilationResult expand_result = expand_expression_nodes(expanded_graph, &remap_info);
|
||||
if (!expand_result.success) {
|
||||
return expand_result;
|
||||
}
|
||||
// Expanding a graph may produce more nodes, not remove any
|
||||
ERR_FAIL_COND_V(expanded_graph.get_nodes_count() < p_graph.get_nodes_count(), make_error("Internal error"));
|
||||
|
||||
const VoxelGraphRuntime::CompilationResult result = _compile(expanded_graph, debug);
|
||||
if (!result.success) {
|
||||
clear();
|
||||
}
|
||||
|
||||
for (PortRemap r : remap_info.user_to_expanded_ports) {
|
||||
_program.user_port_to_expanded_port.insert({ r.original, r.expanded });
|
||||
}
|
||||
for (ExpandedNodeRemap r : remap_info.expanded_to_user_node_ids) {
|
||||
_program.expanded_node_id_to_user_node_id.insert({ r.expanded_node_id, r.original_node_id });
|
||||
}
|
||||
// Remap debug nodes from the execution map to user-facing ones
|
||||
for (uint32_t &debug_node_id : _program.default_execution_map.debug_nodes) {
|
||||
auto it = _program.expanded_node_id_to_user_node_id.find(debug_node_id);
|
||||
if (it != _program.expanded_node_id_to_user_node_id.end()) {
|
||||
debug_node_id = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGraph &graph, bool debug) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
clear();
|
||||
|
||||
std::vector<uint32_t> order;
|
||||
std::vector<uint32_t> terminal_nodes;
|
||||
std::unordered_map<uint32_t, uint32_t> node_id_to_dependency_graph;
|
||||
|
||||
const VoxelGraphNodeDB &type_db = VoxelGraphNodeDB::get_singleton();
|
||||
|
||||
// Not using the generic `get_terminal_nodes` function because our terminal nodes do have outputs
|
||||
graph.for_each_node_const([&terminal_nodes, &type_db](const ProgramGraph::Node &node) {
|
||||
const VoxelGraphNodeDB::NodeType &type = type_db.get_type(node.type_id);
|
||||
if (type.category == VoxelGraphNodeDB::CATEGORY_OUTPUT) {
|
||||
terminal_nodes.push_back(node.id);
|
||||
}
|
||||
});
|
||||
|
||||
if (!debug) {
|
||||
// Exclude debug nodes
|
||||
unordered_remove_if(terminal_nodes, [&graph, &type_db](uint32_t node_id) {
|
||||
const ProgramGraph::Node &node = graph.get_node(node_id);
|
||||
const VoxelGraphNodeDB::NodeType &type = type_db.get_type(node.type_id);
|
||||
return type.debug_only;
|
||||
});
|
||||
}
|
||||
|
||||
graph.find_dependencies(terminal_nodes, order);
|
||||
|
||||
uint32_t xzy_start_index = 0;
|
||||
|
||||
// Optimize parts of the graph that only depend on X and Z,
|
||||
// so they can be moved in the outer loop when blocks are generated, running less times.
|
||||
// Moves them all at the beginning.
|
||||
{
|
||||
std::vector<uint32_t> immediate_deps;
|
||||
std::unordered_set<uint32_t> nodes_depending_on_y;
|
||||
std::vector<uint32_t> order_xz;
|
||||
std::vector<uint32_t> order_xzy;
|
||||
|
||||
for (size_t i = 0; i < order.size(); ++i) {
|
||||
const uint32_t node_id = order[i];
|
||||
const ProgramGraph::Node &node = graph.get_node(node_id);
|
||||
|
||||
bool depends_on_y = false;
|
||||
|
||||
if (node.type_id == VoxelGeneratorGraph::NODE_INPUT_Y) {
|
||||
nodes_depending_on_y.insert(node_id);
|
||||
depends_on_y = true;
|
||||
}
|
||||
|
||||
if (!depends_on_y) {
|
||||
immediate_deps.clear();
|
||||
graph.find_immediate_dependencies(node_id, immediate_deps);
|
||||
|
||||
for (size_t j = 0; j < immediate_deps.size(); ++j) {
|
||||
const uint32_t dep_node_id = immediate_deps[j];
|
||||
|
||||
if (nodes_depending_on_y.find(dep_node_id) != nodes_depending_on_y.end()) {
|
||||
depends_on_y = true;
|
||||
nodes_depending_on_y.insert(node_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (depends_on_y) {
|
||||
order_xzy.push_back(node_id);
|
||||
} else {
|
||||
order_xz.push_back(node_id);
|
||||
}
|
||||
}
|
||||
|
||||
xzy_start_index = order_xz.size();
|
||||
|
||||
//#ifdef DEBUG_ENABLED
|
||||
// const uint32_t order_xz_raw_size = order_xz.size();
|
||||
// const uint32_t *order_xz_raw = order_xz.data();
|
||||
// const uint32_t order_xzy_raw_size = order_xzy.size();
|
||||
// const uint32_t *order_xzy_raw = order_xzy.data();
|
||||
//#endif
|
||||
|
||||
size_t i = 0;
|
||||
for (size_t j = 0; j < order_xz.size(); ++j) {
|
||||
order[i++] = order_xz[j];
|
||||
}
|
||||
for (size_t j = 0; j < order_xzy.size(); ++j) {
|
||||
order[i++] = order_xzy[j];
|
||||
}
|
||||
}
|
||||
|
||||
//#ifdef DEBUG_ENABLED
|
||||
// const uint32_t order_raw_size = order.size();
|
||||
// const uint32_t *order_raw = order.data();
|
||||
//#endif
|
||||
|
||||
struct MemoryHelper {
|
||||
std::vector<BufferSpec> &buffer_specs;
|
||||
unsigned int next_address = 0;
|
||||
|
||||
uint16_t add_binding() {
|
||||
const unsigned int a = next_address;
|
||||
++next_address;
|
||||
BufferSpec bs;
|
||||
bs.address = a;
|
||||
bs.is_binding = true;
|
||||
bs.is_constant = false;
|
||||
bs.users_count = 0;
|
||||
buffer_specs.push_back(bs);
|
||||
return a;
|
||||
}
|
||||
|
||||
uint16_t add_var() {
|
||||
const unsigned int a = next_address;
|
||||
++next_address;
|
||||
BufferSpec bs;
|
||||
bs.address = a;
|
||||
bs.is_binding = false;
|
||||
bs.is_constant = false;
|
||||
bs.users_count = 0;
|
||||
buffer_specs.push_back(bs);
|
||||
return a;
|
||||
}
|
||||
|
||||
uint16_t add_constant(float v) {
|
||||
const unsigned int a = next_address;
|
||||
++next_address;
|
||||
BufferSpec bs;
|
||||
bs.address = a;
|
||||
bs.constant_value = v;
|
||||
bs.is_binding = false;
|
||||
bs.is_constant = true;
|
||||
bs.users_count = 0;
|
||||
buffer_specs.push_back(bs);
|
||||
return a;
|
||||
}
|
||||
};
|
||||
|
||||
MemoryHelper mem{ _program.buffer_specs };
|
||||
|
||||
// Main inputs X, Y, Z
|
||||
_program.x_input_address = mem.add_binding();
|
||||
_program.y_input_address = mem.add_binding();
|
||||
_program.z_input_address = mem.add_binding();
|
||||
|
||||
std::vector<uint16_t> &operations = _program.operations;
|
||||
|
||||
// Run through each node in order, and turn them into program instructions
|
||||
for (size_t order_index = 0; order_index < order.size(); ++order_index) {
|
||||
const uint32_t node_id = order[order_index];
|
||||
const ProgramGraph::Node &node = graph.get_node(node_id);
|
||||
const VoxelGraphNodeDB::NodeType &type = type_db.get_type(node.type_id);
|
||||
|
||||
CRASH_COND(node.inputs.size() != type.inputs.size());
|
||||
CRASH_COND(node.outputs.size() != type.outputs.size());
|
||||
|
||||
if (order_index == xzy_start_index) {
|
||||
_program.xzy_start_op_address = operations.size();
|
||||
}
|
||||
|
||||
const unsigned int dg_node_index = _program.dependency_graph.nodes.size();
|
||||
_program.dependency_graph.nodes.push_back(DependencyGraph::Node());
|
||||
DependencyGraph::Node &dg_node = _program.dependency_graph.nodes.back();
|
||||
dg_node.is_input = false;
|
||||
dg_node.op_address = operations.size();
|
||||
dg_node.first_dependency = _program.dependency_graph.dependencies.size();
|
||||
dg_node.end_dependency = dg_node.first_dependency;
|
||||
dg_node.debug_node_id = node_id;
|
||||
node_id_to_dependency_graph.insert(std::make_pair(node_id, dg_node_index));
|
||||
|
||||
// We still hardcode some of the nodes. Maybe we can abstract them too one day.
|
||||
switch (node.type_id) {
|
||||
case VoxelGeneratorGraph::NODE_CONSTANT: {
|
||||
CRASH_COND(type.outputs.size() != 1);
|
||||
CRASH_COND(type.params.size() != 1);
|
||||
const uint16_t a = mem.add_constant(node.params[0].operator float());
|
||||
_program.output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = a;
|
||||
// Technically not an input or an output, but is a dependency regardless so treat it like an input
|
||||
dg_node.is_input = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Input nodes can appear multiple times in the graph, for convenience.
|
||||
// Multiple instances of the same node will refer to the same data.
|
||||
case VoxelGeneratorGraph::NODE_INPUT_X:
|
||||
_program.output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = _program.x_input_address;
|
||||
dg_node.is_input = true;
|
||||
continue;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_INPUT_Y:
|
||||
_program.output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = _program.y_input_address;
|
||||
dg_node.is_input = true;
|
||||
continue;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_INPUT_Z:
|
||||
_program.output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = _program.z_input_address;
|
||||
dg_node.is_input = true;
|
||||
continue;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_SDF_PREVIEW:
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add actual operation
|
||||
|
||||
CRASH_COND(node.type_id > 0xff);
|
||||
|
||||
if (order_index == xzy_start_index) {
|
||||
_program.default_execution_map.xzy_start_index = _program.default_execution_map.operation_adresses.size();
|
||||
}
|
||||
_program.default_execution_map.operation_adresses.push_back(operations.size());
|
||||
if (debug) {
|
||||
// Will be remapped later if the node is an expanded one
|
||||
_program.default_execution_map.debug_nodes.push_back(node_id);
|
||||
}
|
||||
|
||||
operations.push_back(node.type_id);
|
||||
|
||||
// Inputs and outputs use a convention so we can have generic code for them.
|
||||
// Parameters are more specific, and may be affected by alignment so better just do them by hand
|
||||
|
||||
// Add inputs
|
||||
for (size_t j = 0; j < type.inputs.size(); ++j) {
|
||||
uint16_t a;
|
||||
|
||||
if (node.inputs[j].connections.size() == 0) {
|
||||
// No input, default it
|
||||
CRASH_COND(j >= node.default_inputs.size());
|
||||
float defval = node.default_inputs[j];
|
||||
a = mem.add_constant(defval);
|
||||
|
||||
} else {
|
||||
ProgramGraph::PortLocation src_port = node.inputs[j].connections[0];
|
||||
const uint16_t *aptr = _program.output_port_addresses.getptr(src_port);
|
||||
// Previous node ports must have been registered
|
||||
CRASH_COND(aptr == nullptr);
|
||||
a = *aptr;
|
||||
|
||||
// Register dependency
|
||||
auto it = node_id_to_dependency_graph.find(src_port.node_id);
|
||||
CRASH_COND(it == node_id_to_dependency_graph.end());
|
||||
CRASH_COND(it->second >= _program.dependency_graph.nodes.size());
|
||||
_program.dependency_graph.dependencies.push_back(it->second);
|
||||
++dg_node.end_dependency;
|
||||
}
|
||||
|
||||
operations.push_back(a);
|
||||
|
||||
BufferSpec &bs = _program.buffer_specs[a];
|
||||
++bs.users_count;
|
||||
}
|
||||
|
||||
// Add outputs
|
||||
for (size_t j = 0; j < type.outputs.size(); ++j) {
|
||||
const uint16_t a = mem.add_var();
|
||||
|
||||
// This will be used by next nodes
|
||||
const ProgramGraph::PortLocation op{ node_id, static_cast<uint32_t>(j) };
|
||||
_program.output_port_addresses[op] = a;
|
||||
|
||||
operations.push_back(a);
|
||||
}
|
||||
|
||||
// Add space for params size, default is no params so size is 0
|
||||
size_t params_size_index = operations.size();
|
||||
operations.push_back(0);
|
||||
|
||||
// Get params, copy resources when used, and hold a reference to them
|
||||
std::vector<Variant> params_copy;
|
||||
params_copy.resize(node.params.size());
|
||||
for (size_t i = 0; i < node.params.size(); ++i) {
|
||||
Variant v = node.params[i];
|
||||
|
||||
if (v.get_type() == Variant::OBJECT) {
|
||||
Ref<Resource> res = v;
|
||||
|
||||
if (res.is_null()) {
|
||||
// duplicate() is only available in Resource,
|
||||
// so we have to limit to this instead of Reference or Object
|
||||
CompilationResult result;
|
||||
result.success = false;
|
||||
result.message = ZN_TTR("A parameter is an object but does not inherit Resource");
|
||||
result.node_id = node_id;
|
||||
return result;
|
||||
}
|
||||
|
||||
res = res->duplicate();
|
||||
|
||||
_program.ref_resources.push_back(res);
|
||||
v = res;
|
||||
}
|
||||
|
||||
params_copy[i] = v;
|
||||
}
|
||||
|
||||
if (type.compile_func != nullptr) {
|
||||
CompileContext ctx(/**node,*/ operations, _program.heap_resources, params_copy);
|
||||
type.compile_func(ctx);
|
||||
if (ctx.has_error()) {
|
||||
CompilationResult result;
|
||||
result.success = false;
|
||||
result.message = ctx.get_error_message();
|
||||
result.node_id = node_id;
|
||||
return result;
|
||||
}
|
||||
const size_t params_size = ctx.get_params_size_in_words();
|
||||
CRASH_COND(params_size > std::numeric_limits<uint16_t>::max());
|
||||
operations[params_size_index] = params_size;
|
||||
}
|
||||
|
||||
if (type.category == VoxelGraphNodeDB::CATEGORY_OUTPUT) {
|
||||
CRASH_COND(node.outputs.size() != 1);
|
||||
|
||||
if (_program.outputs_count == _program.outputs.size()) {
|
||||
CompilationResult result;
|
||||
result.success = false;
|
||||
result.message = ZN_TTR("Maximum number of outputs has been reached");
|
||||
result.node_id = node_id;
|
||||
return result;
|
||||
}
|
||||
|
||||
{
|
||||
const uint16_t *aptr = _program.output_port_addresses.getptr(ProgramGraph::PortLocation{ node_id, 0 });
|
||||
// Previous node ports must have been registered
|
||||
CRASH_COND(aptr == nullptr);
|
||||
OutputInfo &output_info = _program.outputs[_program.outputs_count];
|
||||
output_info.buffer_address = *aptr;
|
||||
output_info.dependency_graph_node_index = dg_node_index;
|
||||
output_info.node_id = node_id;
|
||||
++_program.outputs_count;
|
||||
}
|
||||
|
||||
// Add fake user for output ports so they can pass the local users check in optimizations
|
||||
for (unsigned int j = 0; j < type.outputs.size(); ++j) {
|
||||
const ProgramGraph::PortLocation loc{ node_id, j };
|
||||
const uint16_t *aptr = _program.output_port_addresses.getptr(loc);
|
||||
CRASH_COND(aptr == nullptr);
|
||||
BufferSpec &bs = _program.buffer_specs[*aptr];
|
||||
// Not expecting existing users on that port
|
||||
ERR_FAIL_COND_V(bs.users_count != 0, CompilationResult());
|
||||
++bs.users_count;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef VOXEL_DEBUG_GRAPH_PROG_SENTINEL
|
||||
// Append a special value after each operation
|
||||
append(operations, VOXEL_DEBUG_GRAPH_PROG_SENTINEL);
|
||||
#endif
|
||||
}
|
||||
|
||||
_program.buffer_count = mem.next_address;
|
||||
|
||||
ZN_PRINT_VERBOSE(format("Compiled voxel graph. Program size: {}b, buffers: {}",
|
||||
_program.operations.size() * sizeof(uint16_t), _program.buffer_count));
|
||||
|
||||
CompilationResult result;
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
static Span<const uint16_t> get_outputs_from_op_address(Span<const uint16_t> operations, uint16_t op_address) {
|
||||
const uint16_t opid = operations[op_address];
|
||||
const VoxelGraphNodeDB::NodeType &node_type = VoxelGraphNodeDB::get_singleton().get_type(opid);
|
||||
@ -1250,176 +549,4 @@ bool VoxelGraphRuntime::try_get_output_port_address(ProgramGraph::PortLocation p
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void VoxelGraphRuntime::ShaderGenContext::require_lib_code(const char *lib_name, const char *code) {
|
||||
_code_gen.require_lib_code(lib_name, code);
|
||||
}
|
||||
|
||||
void VoxelGraphRuntime::ShaderGenContext::require_lib_code(const char *lib_name, const char **code) {
|
||||
_code_gen.require_lib_code(lib_name, code);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::generate_shader(
|
||||
const ProgramGraph &p_graph, FwdMutableStdString output) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
|
||||
ProgramGraph expanded_graph;
|
||||
expanded_graph.copy_from(p_graph, false);
|
||||
const VoxelGraphRuntime::CompilationResult expand_result = expand_expression_nodes(expanded_graph, nullptr);
|
||||
if (!expand_result.success) {
|
||||
return expand_result;
|
||||
}
|
||||
// Expanding a graph may produce more nodes, not remove any
|
||||
ZN_ASSERT_RETURN_V(expanded_graph.get_nodes_count() >= p_graph.get_nodes_count(), make_error("Internal error"));
|
||||
|
||||
std::vector<uint32_t> order;
|
||||
std::vector<uint32_t> terminal_nodes;
|
||||
|
||||
const VoxelGraphNodeDB &type_db = VoxelGraphNodeDB::get_singleton();
|
||||
|
||||
// Only getting SDF for now, as this is the first use case I want to test this feature with
|
||||
expanded_graph.for_each_node_const([&terminal_nodes, &type_db](const ProgramGraph::Node &node) {
|
||||
if (node.type_id == VoxelGeneratorGraph::NODE_OUTPUT_SDF) {
|
||||
terminal_nodes.push_back(node.id);
|
||||
}
|
||||
});
|
||||
|
||||
if (terminal_nodes.size() == 0) {
|
||||
return make_error("The graph must contain an SDF output.");
|
||||
}
|
||||
|
||||
// Exclude debug nodes
|
||||
// unordered_remove_if(terminal_nodes, [&expanded_graph, &type_db](uint32_t node_id) {
|
||||
// const ProgramGraph::Node &node = expanded_graph.get_node(node_id);
|
||||
// const VoxelGraphNodeDB::NodeType &type = type_db.get_type(node.type_id);
|
||||
// return type.debug_only;
|
||||
// });
|
||||
|
||||
expanded_graph.find_dependencies(terminal_nodes, order);
|
||||
|
||||
std::stringstream main_ss;
|
||||
std::stringstream lib_ss;
|
||||
CodeGenHelper codegen(main_ss, lib_ss);
|
||||
|
||||
codegen.add("float get_sdf(vec3 pos) {\n");
|
||||
codegen.indent();
|
||||
|
||||
std::unordered_map<ProgramGraph::PortLocation, std::string> port_to_var;
|
||||
FixedArray<const char *, 8> input_names;
|
||||
FixedArray<const char *, 8> output_names;
|
||||
|
||||
for (const uint32_t node_id : order) {
|
||||
const ProgramGraph::Node &node = expanded_graph.get_node(node_id);
|
||||
const VoxelGraphNodeDB::NodeType node_type = type_db.get_type(node.type_id);
|
||||
|
||||
switch (node.type_id) {
|
||||
case VoxelGeneratorGraph::NODE_INPUT_X: {
|
||||
ZN_ASSERT(node.outputs.size() == 1);
|
||||
const ProgramGraph::PortLocation output_port{ node_id, 0 };
|
||||
port_to_var.insert({ output_port, "pos.x" });
|
||||
continue;
|
||||
}
|
||||
case VoxelGeneratorGraph::NODE_INPUT_Y: {
|
||||
ZN_ASSERT(node.outputs.size() == 1);
|
||||
const ProgramGraph::PortLocation output_port{ node_id, 0 };
|
||||
port_to_var.insert({ output_port, "pos.y" });
|
||||
continue;
|
||||
}
|
||||
case VoxelGeneratorGraph::NODE_INPUT_Z: {
|
||||
ZN_ASSERT(node.outputs.size() == 1);
|
||||
const ProgramGraph::PortLocation output_port{ node_id, 0 };
|
||||
port_to_var.insert({ output_port, "pos.z" });
|
||||
continue;
|
||||
}
|
||||
case VoxelGeneratorGraph::NODE_CONSTANT: {
|
||||
ZN_ASSERT(node.outputs.size() == 1);
|
||||
const ProgramGraph::PortLocation output_port{ node_id, 0 };
|
||||
std::string name;
|
||||
codegen.generate_var_name(name);
|
||||
port_to_var.insert({ output_port, name });
|
||||
ZN_ASSERT(node.params.size() == 1);
|
||||
codegen.add_format("float {} = {};\n", name, float(node.params[0]));
|
||||
continue;
|
||||
}
|
||||
case VoxelGeneratorGraph::NODE_OUTPUT_SDF: {
|
||||
ZN_ASSERT(node.outputs.size() == 1);
|
||||
const ProgramGraph::Port &input_port = node.inputs[0];
|
||||
if (input_port.connections.size() > 0) {
|
||||
ZN_ASSERT(input_port.connections.size() == 1);
|
||||
auto it = port_to_var.find(input_port.connections[0]);
|
||||
ZN_ASSERT(it != port_to_var.end());
|
||||
codegen.add_format("return {};\n", it->second);
|
||||
} else {
|
||||
codegen.add("return 0.0;\n");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (node_type.shader_gen_func == nullptr) {
|
||||
return make_error("A node does not support conversion to shader.", node_id);
|
||||
}
|
||||
|
||||
for (unsigned int port_index = 0; port_index < node.inputs.size(); ++port_index) {
|
||||
const ProgramGraph::Port &input_port = node.inputs[port_index];
|
||||
if (input_port.connections.size() > 0) {
|
||||
ZN_ASSERT(input_port.connections.size() == 1);
|
||||
auto it = port_to_var.find(input_port.connections[0]);
|
||||
ZN_ASSERT(it != port_to_var.end());
|
||||
input_names[port_index] = it->second.c_str();
|
||||
} else {
|
||||
std::string var_name;
|
||||
codegen.generate_var_name(var_name);
|
||||
auto p = port_to_var.insert({ { node_id, port_index }, var_name });
|
||||
ZN_ASSERT(p.second);
|
||||
const std::string &name = p.first->second;
|
||||
input_names[port_index] = name.c_str();
|
||||
codegen.add_format("float {} = {};\n", name, float(node.default_inputs[port_index]));
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int port_index = 0; port_index < node.outputs.size(); ++port_index) {
|
||||
const ProgramGraph::Port &output_port = node.outputs[port_index];
|
||||
std::string var_name;
|
||||
codegen.generate_var_name(var_name);
|
||||
auto p = port_to_var.insert({ { node_id, port_index }, var_name });
|
||||
ZN_ASSERT(p.second);
|
||||
output_names[port_index] = p.first->second.c_str();
|
||||
codegen.add_format("float {};\n", var_name.c_str());
|
||||
}
|
||||
|
||||
codegen.add("{\n");
|
||||
codegen.indent();
|
||||
|
||||
ShaderGenContext ctx(node.params, to_span(input_names, node.inputs.size()),
|
||||
to_span(output_names, node.outputs.size()), codegen);
|
||||
node_type.shader_gen_func(ctx);
|
||||
|
||||
if (ctx.has_error()) {
|
||||
CompilationResult result;
|
||||
result.success = false;
|
||||
result.message = ctx.get_error_message();
|
||||
result.node_id = node_id;
|
||||
return result;
|
||||
}
|
||||
|
||||
codegen.dedent();
|
||||
codegen.add("}\n");
|
||||
}
|
||||
|
||||
codegen.dedent();
|
||||
codegen.add("}\n");
|
||||
|
||||
codegen.print(output);
|
||||
|
||||
CompilationResult result;
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace zylann::voxel
|
||||
|
@ -5,13 +5,14 @@
|
||||
#include "../../util/math/vector3f.h"
|
||||
#include "../../util/math/vector3i.h"
|
||||
#include "../../util/span.h"
|
||||
#include "code_gen_helper.h"
|
||||
#include "program_graph.h"
|
||||
|
||||
#include <core/object/ref_counted.h>
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
class VoxelGraphNodeDB;
|
||||
|
||||
// CPU VM to execute a voxel graph generator.
|
||||
// This is a more generic class implementing the core of a 3D expression processing system.
|
||||
// Some of the logic dedicated to voxel data is moved in other classes.
|
||||
@ -23,6 +24,14 @@ public:
|
||||
bool success = false;
|
||||
int node_id = -1;
|
||||
String message;
|
||||
|
||||
static CompilationResult make_error(const char *p_message, int p_node_id = -1) {
|
||||
VoxelGraphRuntime::CompilationResult res;
|
||||
res.success = false;
|
||||
res.node_id = p_node_id;
|
||||
res.message = p_message;
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
// Contains values of a node output
|
||||
@ -178,99 +187,11 @@ public:
|
||||
// Gets the buffer address of a specific output port
|
||||
bool try_get_output_port_address(ProgramGraph::PortLocation port, uint16_t &out_address) const;
|
||||
|
||||
static CompilationResult generate_shader(const ProgramGraph &p_graph, FwdMutableStdString output);
|
||||
|
||||
struct HeapResource {
|
||||
void *ptr;
|
||||
void (*deleter)(void *p);
|
||||
};
|
||||
|
||||
// Functions usable by node implementations during the compilation stage
|
||||
class CompileContext {
|
||||
public:
|
||||
CompileContext(/*const ProgramGraph::Node &node,*/ std::vector<uint16_t> &program,
|
||||
std::vector<HeapResource> &heap_resources, std::vector<Variant> ¶ms) :
|
||||
/*_node(node),*/ _program(program), _heap_resources(heap_resources), _params(params) {}
|
||||
|
||||
Variant get_param(size_t i) const {
|
||||
CRASH_COND(i > _params.size());
|
||||
return _params[i];
|
||||
}
|
||||
|
||||
// Typical use is to pass a struct containing all compile-time arguments the operation will need
|
||||
template <typename T>
|
||||
void set_params(T params) {
|
||||
// Can be called only once per node
|
||||
CRASH_COND(_params_added);
|
||||
// We will need to align memory, so the struct will not be immediately stored here.
|
||||
// Instead we put a header that tells how much to advance in order to reach the beginning of the struct,
|
||||
// which will be at an aligned position.
|
||||
// We align to the maximum alignment between the struct,
|
||||
// and the type of word we store inside the program buffer, which is uint16.
|
||||
const size_t params_alignment = math::max(alignof(T), alignof(uint16_t));
|
||||
const size_t params_offset_index = _program.size();
|
||||
// Prepare space to store the offset (at least 1 since that header is one word)
|
||||
_program.push_back(1);
|
||||
// Align memory for the struct.
|
||||
// Note, we index with words, not bytes.
|
||||
const size_t struct_offset =
|
||||
math::alignup(_program.size() * sizeof(uint16_t), params_alignment) / sizeof(uint16_t);
|
||||
if (struct_offset > _program.size()) {
|
||||
_program.resize(struct_offset);
|
||||
}
|
||||
// Write offset in header
|
||||
_program[params_offset_index] = struct_offset - params_offset_index;
|
||||
// Allocate space for the struct. It is measured in words, so it can be up to 1 byte larger.
|
||||
_params_size_in_words = (sizeof(T) + sizeof(uint16_t) - 1) / sizeof(uint16_t);
|
||||
_program.resize(_program.size() + _params_size_in_words);
|
||||
// Write struct
|
||||
T &p = *reinterpret_cast<T *>(&_program[struct_offset]);
|
||||
p = params;
|
||||
|
||||
_params_added = true;
|
||||
}
|
||||
|
||||
// In case the compilation step produces a resource to be deleted
|
||||
template <typename T>
|
||||
void add_memdelete_cleanup(T *ptr) {
|
||||
HeapResource hr;
|
||||
hr.ptr = ptr;
|
||||
hr.deleter = [](void *p) {
|
||||
// TODO We have no guarantee it was allocated with memnew :|
|
||||
T *tp = reinterpret_cast<T *>(p);
|
||||
memdelete(tp);
|
||||
};
|
||||
_heap_resources.push_back(hr);
|
||||
}
|
||||
|
||||
void make_error(String message) {
|
||||
_error_message = message;
|
||||
_has_error = true;
|
||||
}
|
||||
|
||||
bool has_error() const {
|
||||
return _has_error;
|
||||
}
|
||||
|
||||
const String &get_error_message() const {
|
||||
return _error_message;
|
||||
}
|
||||
|
||||
size_t get_params_size_in_words() const {
|
||||
return _params_size_in_words;
|
||||
}
|
||||
|
||||
private:
|
||||
//const ProgramGraph::Node &_node;
|
||||
std::vector<uint16_t> &_program;
|
||||
std::vector<HeapResource> &_heap_resources;
|
||||
std::vector<Variant> &_params;
|
||||
String _error_message;
|
||||
size_t _params_size_in_words = 0;
|
||||
bool _has_error = false;
|
||||
bool _params_added = false;
|
||||
};
|
||||
|
||||
class _ProcessContext {
|
||||
public:
|
||||
inline _ProcessContext(const Span<const uint16_t> inputs, const Span<const uint16_t> outputs,
|
||||
@ -369,62 +290,11 @@ public:
|
||||
Span<Buffer> _buffers;
|
||||
};
|
||||
|
||||
class ShaderGenContext {
|
||||
public:
|
||||
ShaderGenContext(const std::vector<Variant> ¶ms, Span<const char *> input_names,
|
||||
Span<const char *> output_names, CodeGenHelper &code_gen) :
|
||||
_params(params), _input_names(input_names), _output_names(output_names), _code_gen(code_gen) {}
|
||||
|
||||
Variant get_param(size_t i) const {
|
||||
CRASH_COND(i > _params.size());
|
||||
return _params[i];
|
||||
}
|
||||
|
||||
const char *get_input_name(unsigned int i) const {
|
||||
return _input_names[i];
|
||||
}
|
||||
|
||||
const char *get_output_name(unsigned int i) const {
|
||||
return _output_names[i];
|
||||
}
|
||||
|
||||
void make_error(String message) {
|
||||
_error_message = message;
|
||||
_has_error = true;
|
||||
}
|
||||
|
||||
bool has_error() const {
|
||||
return _has_error;
|
||||
}
|
||||
|
||||
const String &get_error_message() const {
|
||||
return _error_message;
|
||||
}
|
||||
|
||||
template <typename... TN>
|
||||
void add_format(const char *fmt, const TN &...an) {
|
||||
_code_gen.add_format(fmt, an...);
|
||||
}
|
||||
|
||||
void require_lib_code(const char *lib_name, const char *code);
|
||||
void require_lib_code(const char *lib_name, const char **code);
|
||||
|
||||
private:
|
||||
const std::vector<Variant> &_params;
|
||||
Span<const char *> _input_names;
|
||||
Span<const char *> _output_names;
|
||||
CodeGenHelper &_code_gen;
|
||||
String _error_message;
|
||||
bool _has_error;
|
||||
};
|
||||
|
||||
typedef void (*CompileFunc)(CompileContext &);
|
||||
typedef void (*ProcessBufferFunc)(ProcessBufferContext &);
|
||||
typedef void (*RangeAnalysisFunc)(RangeAnalysisContext &);
|
||||
typedef void (*ShaderGenFunc)(ShaderGenContext &);
|
||||
|
||||
private:
|
||||
CompilationResult _compile(const ProgramGraph &graph, bool debug);
|
||||
CompilationResult _compile(const ProgramGraph &graph, bool debug, const VoxelGraphNodeDB &type_db);
|
||||
|
||||
bool is_operation_constant(const State &state, uint16_t op_address) const;
|
||||
|
||||
@ -441,6 +311,7 @@ private:
|
||||
bool is_binding;
|
||||
};
|
||||
|
||||
// Pre-processed, read-only graph used for runtime optimizations.
|
||||
struct DependencyGraph {
|
||||
struct Node {
|
||||
uint16_t first_dependency;
|
||||
|
181
generators/graph/voxel_graph_shader_generator.cpp
Normal file
181
generators/graph/voxel_graph_shader_generator.cpp
Normal file
@ -0,0 +1,181 @@
|
||||
#include "voxel_graph_shader_generator.h"
|
||||
#include "../../util/profiling.h"
|
||||
#include "../../util/string_funcs.h"
|
||||
#include "voxel_graph_compiler.h"
|
||||
#include "voxel_graph_node_db.h"
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
void ShaderGenContext::require_lib_code(const char *lib_name, const char *code) {
|
||||
_code_gen.require_lib_code(lib_name, code);
|
||||
}
|
||||
|
||||
void ShaderGenContext::require_lib_code(const char *lib_name, const char **code) {
|
||||
_code_gen.require_lib_code(lib_name, code);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
VoxelGraphRuntime::CompilationResult generate_shader(const ProgramGraph &p_graph, FwdMutableStdString output) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
|
||||
const VoxelGraphNodeDB &type_db = VoxelGraphNodeDB::get_singleton();
|
||||
|
||||
ProgramGraph expanded_graph;
|
||||
expanded_graph.copy_from(p_graph, false);
|
||||
const VoxelGraphRuntime::CompilationResult expand_result =
|
||||
expand_expression_nodes(expanded_graph, type_db, nullptr);
|
||||
if (!expand_result.success) {
|
||||
return expand_result;
|
||||
}
|
||||
// Expanding a graph may produce more nodes, not remove any
|
||||
ZN_ASSERT_RETURN_V(expanded_graph.get_nodes_count() >= p_graph.get_nodes_count(),
|
||||
VoxelGraphRuntime::CompilationResult::make_error("Internal error"));
|
||||
|
||||
std::vector<uint32_t> order;
|
||||
std::vector<uint32_t> terminal_nodes;
|
||||
|
||||
// Only getting SDF for now, as this is the first use case I want to test this feature with
|
||||
expanded_graph.for_each_node_const([&terminal_nodes, &type_db](const ProgramGraph::Node &node) {
|
||||
if (node.type_id == VoxelGeneratorGraph::NODE_OUTPUT_SDF) {
|
||||
terminal_nodes.push_back(node.id);
|
||||
}
|
||||
});
|
||||
|
||||
if (terminal_nodes.size() == 0) {
|
||||
return VoxelGraphRuntime::CompilationResult::make_error("The graph must contain an SDF output.");
|
||||
}
|
||||
|
||||
// Exclude debug nodes
|
||||
// unordered_remove_if(terminal_nodes, [&expanded_graph, &type_db](uint32_t node_id) {
|
||||
// const ProgramGraph::Node &node = expanded_graph.get_node(node_id);
|
||||
// const VoxelGraphNodeDB::NodeType &type = type_db.get_type(node.type_id);
|
||||
// return type.debug_only;
|
||||
// });
|
||||
|
||||
expanded_graph.find_dependencies(terminal_nodes, order);
|
||||
|
||||
std::stringstream main_ss;
|
||||
std::stringstream lib_ss;
|
||||
CodeGenHelper codegen(main_ss, lib_ss);
|
||||
|
||||
codegen.add("float get_sdf(vec3 pos) {\n");
|
||||
codegen.indent();
|
||||
|
||||
std::unordered_map<ProgramGraph::PortLocation, std::string> port_to_var;
|
||||
FixedArray<const char *, 8> input_names;
|
||||
FixedArray<const char *, 8> output_names;
|
||||
|
||||
for (const uint32_t node_id : order) {
|
||||
const ProgramGraph::Node &node = expanded_graph.get_node(node_id);
|
||||
const VoxelGraphNodeDB::NodeType node_type = type_db.get_type(node.type_id);
|
||||
|
||||
switch (node.type_id) {
|
||||
case VoxelGeneratorGraph::NODE_INPUT_X: {
|
||||
ZN_ASSERT(node.outputs.size() == 1);
|
||||
const ProgramGraph::PortLocation output_port{ node_id, 0 };
|
||||
port_to_var.insert({ output_port, "pos.x" });
|
||||
continue;
|
||||
}
|
||||
case VoxelGeneratorGraph::NODE_INPUT_Y: {
|
||||
ZN_ASSERT(node.outputs.size() == 1);
|
||||
const ProgramGraph::PortLocation output_port{ node_id, 0 };
|
||||
port_to_var.insert({ output_port, "pos.y" });
|
||||
continue;
|
||||
}
|
||||
case VoxelGeneratorGraph::NODE_INPUT_Z: {
|
||||
ZN_ASSERT(node.outputs.size() == 1);
|
||||
const ProgramGraph::PortLocation output_port{ node_id, 0 };
|
||||
port_to_var.insert({ output_port, "pos.z" });
|
||||
continue;
|
||||
}
|
||||
case VoxelGeneratorGraph::NODE_CONSTANT: {
|
||||
ZN_ASSERT(node.outputs.size() == 1);
|
||||
const ProgramGraph::PortLocation output_port{ node_id, 0 };
|
||||
std::string name;
|
||||
codegen.generate_var_name(name);
|
||||
port_to_var.insert({ output_port, name });
|
||||
ZN_ASSERT(node.params.size() == 1);
|
||||
codegen.add_format("float {} = {};\n", name, float(node.params[0]));
|
||||
continue;
|
||||
}
|
||||
case VoxelGeneratorGraph::NODE_OUTPUT_SDF: {
|
||||
ZN_ASSERT(node.outputs.size() == 1);
|
||||
const ProgramGraph::Port &input_port = node.inputs[0];
|
||||
if (input_port.connections.size() > 0) {
|
||||
ZN_ASSERT(input_port.connections.size() == 1);
|
||||
auto it = port_to_var.find(input_port.connections[0]);
|
||||
ZN_ASSERT(it != port_to_var.end());
|
||||
codegen.add_format("return {};\n", it->second);
|
||||
} else {
|
||||
codegen.add("return 0.0;\n");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (node_type.shader_gen_func == nullptr) {
|
||||
return VoxelGraphRuntime::CompilationResult::make_error(
|
||||
"A node does not support conversion to shader.", node_id);
|
||||
}
|
||||
|
||||
for (unsigned int port_index = 0; port_index < node.inputs.size(); ++port_index) {
|
||||
const ProgramGraph::Port &input_port = node.inputs[port_index];
|
||||
if (input_port.connections.size() > 0) {
|
||||
ZN_ASSERT(input_port.connections.size() == 1);
|
||||
auto it = port_to_var.find(input_port.connections[0]);
|
||||
ZN_ASSERT(it != port_to_var.end());
|
||||
input_names[port_index] = it->second.c_str();
|
||||
} else {
|
||||
std::string var_name;
|
||||
codegen.generate_var_name(var_name);
|
||||
auto p = port_to_var.insert({ { node_id, port_index }, var_name });
|
||||
ZN_ASSERT(p.second);
|
||||
const std::string &name = p.first->second;
|
||||
input_names[port_index] = name.c_str();
|
||||
codegen.add_format("float {} = {};\n", name, float(node.default_inputs[port_index]));
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int port_index = 0; port_index < node.outputs.size(); ++port_index) {
|
||||
const ProgramGraph::Port &output_port = node.outputs[port_index];
|
||||
std::string var_name;
|
||||
codegen.generate_var_name(var_name);
|
||||
auto p = port_to_var.insert({ { node_id, port_index }, var_name });
|
||||
ZN_ASSERT(p.second);
|
||||
output_names[port_index] = p.first->second.c_str();
|
||||
codegen.add_format("float {};\n", var_name.c_str());
|
||||
}
|
||||
|
||||
codegen.add("{\n");
|
||||
codegen.indent();
|
||||
|
||||
ShaderGenContext ctx(node.params, to_span(input_names, node.inputs.size()),
|
||||
to_span(output_names, node.outputs.size()), codegen);
|
||||
node_type.shader_gen_func(ctx);
|
||||
|
||||
if (ctx.has_error()) {
|
||||
VoxelGraphRuntime::CompilationResult result;
|
||||
result.success = false;
|
||||
result.message = ctx.get_error_message();
|
||||
result.node_id = node_id;
|
||||
return result;
|
||||
}
|
||||
|
||||
codegen.dedent();
|
||||
codegen.add("}\n");
|
||||
}
|
||||
|
||||
codegen.dedent();
|
||||
codegen.add("}\n");
|
||||
|
||||
codegen.print(output);
|
||||
|
||||
VoxelGraphRuntime::CompilationResult result;
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace zylann::voxel
|
68
generators/graph/voxel_graph_shader_generator.h
Normal file
68
generators/graph/voxel_graph_shader_generator.h
Normal file
@ -0,0 +1,68 @@
|
||||
#ifndef VOXEL_GRAPH_SHADER_GENERATOR_H
|
||||
#define VOXEL_GRAPH_SHADER_GENERATOR_H
|
||||
|
||||
#include "../../util/errors.h"
|
||||
#include "../../util/span.h"
|
||||
#include "code_gen_helper.h"
|
||||
#include "voxel_graph_runtime.h"
|
||||
#include <core/variant/variant.h>
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
VoxelGraphRuntime::CompilationResult generate_shader(const ProgramGraph &p_graph, FwdMutableStdString output);
|
||||
|
||||
// Sent as argument to functions implementing generator nodes, in order to generate shader code.
|
||||
class ShaderGenContext {
|
||||
public:
|
||||
ShaderGenContext(const std::vector<Variant> ¶ms, Span<const char *> input_names,
|
||||
Span<const char *> output_names, CodeGenHelper &code_gen) :
|
||||
_params(params), _input_names(input_names), _output_names(output_names), _code_gen(code_gen) {}
|
||||
|
||||
Variant get_param(size_t i) const {
|
||||
ZN_ASSERT(i < _params.size());
|
||||
return _params[i];
|
||||
}
|
||||
|
||||
const char *get_input_name(unsigned int i) const {
|
||||
return _input_names[i];
|
||||
}
|
||||
|
||||
const char *get_output_name(unsigned int i) const {
|
||||
return _output_names[i];
|
||||
}
|
||||
|
||||
void make_error(String message) {
|
||||
_error_message = message;
|
||||
_has_error = true;
|
||||
}
|
||||
|
||||
bool has_error() const {
|
||||
return _has_error;
|
||||
}
|
||||
|
||||
const String &get_error_message() const {
|
||||
return _error_message;
|
||||
}
|
||||
|
||||
template <typename... TN>
|
||||
void add_format(const char *fmt, const TN &...an) {
|
||||
_code_gen.add_format(fmt, an...);
|
||||
}
|
||||
|
||||
void require_lib_code(const char *lib_name, const char *code);
|
||||
void require_lib_code(const char *lib_name, const char **code);
|
||||
|
||||
private:
|
||||
const std::vector<Variant> &_params;
|
||||
Span<const char *> _input_names;
|
||||
Span<const char *> _output_names;
|
||||
CodeGenHelper &_code_gen;
|
||||
String _error_message;
|
||||
bool _has_error;
|
||||
};
|
||||
|
||||
typedef void (*ShaderGenFunc)(ShaderGenContext &);
|
||||
|
||||
} // namespace zylann::voxel
|
||||
|
||||
#endif // VOXEL_GRAPH_SHADER_GENERATOR_H
|
Loading…
x
Reference in New Issue
Block a user