diff --git a/editor/graph/voxel_graph_editor.cpp b/editor/graph/voxel_graph_editor.cpp index 8094e93a..2131e9b5 100644 --- a/editor/graph/voxel_graph_editor.cpp +++ b/editor/graph/voxel_graph_editor.cpp @@ -1,6 +1,7 @@ #include "voxel_graph_editor.h" #include "../../generators/graph/voxel_generator_graph.h" #include "../../generators/graph/voxel_graph_node_db.h" +#include "../../terrain/voxel_node.h" #include "../../util/macros.h" #include @@ -69,13 +70,15 @@ public: enabled_checkbox = memnew(CheckBox); enabled_checkbox->set_text(TTR("Enabled")); + enabled_checkbox->connect("toggled", this, "_on_enabled_checkbox_toggled"); vb->add_child(enabled_checkbox); Label *tip = memnew(Label); // TODO Had to use `\n` and disable autowrap, otherwise the popup height becomes crazy high // See https://github.com/godotengine/godot/issues/47005 tip->set_text(TTR("When enabled, hover node output labels to\ninspect their " - "estimated range within the\nconfigured area.")); + "estimated range within the\nconfigured area.\n" + "Nodes that may be optimized out locally will be greyed out.")); //tip->set_autowrap(true); tip->set_modulate(Color(1.f, 1.f, 1.f, 0.8f)); vb->add_child(tip); @@ -107,7 +110,15 @@ public: } private: - static void add_row(String text, SpinBox *&sb, GridContainer *parent, float defval) { + void _on_enabled_checkbox_toggled(bool enabled) { + emit_signal("analysis_toggled", enabled); + } + + void _on_area_spinbox_value_changed(float value) { + emit_signal("area_changed"); + } + + void add_row(String text, SpinBox *&sb, GridContainer *parent, float defval) { sb = memnew(SpinBox); sb->set_min(-99999.f); sb->set_max(99999.f); @@ -117,6 +128,17 @@ private: label->set_text(text); parent->add_child(label); parent->add_child(sb); + sb->connect("value_changed", this, "_on_area_spinbox_value_changed"); + } + + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("_on_enabled_checkbox_toggled", "enabled"), + &VoxelRangeAnalysisDialog::_on_enabled_checkbox_toggled); + ClassDB::bind_method(D_METHOD("_on_area_spinbox_value_changed", "value"), + &VoxelRangeAnalysisDialog::_on_area_spinbox_value_changed); + + ADD_SIGNAL(MethodInfo("analysis_toggled", PropertyInfo(Variant::BOOL, "enabled"))); + ADD_SIGNAL(MethodInfo("area_changed")); } CheckBox *enabled_checkbox; @@ -190,6 +212,8 @@ VoxelGraphEditor::VoxelGraphEditor() { add_child(_context_menu); _range_analysis_dialog = memnew(VoxelRangeAnalysisDialog); + _range_analysis_dialog->connect("analysis_toggled", this, "_on_range_analysis_toggled"); + _range_analysis_dialog->connect("area_changed", this, "_on_range_analysis_area_changed"); add_child(_range_analysis_dialog); } @@ -210,6 +234,8 @@ void VoxelGraphEditor::set_graph(Ref graph) { _graph->connect(VoxelGeneratorGraph::SIGNAL_NODE_NAME_CHANGED, this, "_on_graph_node_name_changed"); } + _debug_renderer.clear(); + build_gui_from_graph(); } @@ -217,6 +243,17 @@ void VoxelGraphEditor::set_undo_redo(UndoRedo *undo_redo) { _undo_redo = undo_redo; } +void VoxelGraphEditor::set_voxel_node(VoxelNode *node) { + _voxel_node = node; + if (_voxel_node == nullptr) { + PRINT_VERBOSE("Reference node for VoxelGraph previews: null"); + _debug_renderer.set_world(nullptr); + } else { + PRINT_VERBOSE(String("Reference node for VoxelGraph previews: {0}").format(varray(node->get_path()))); + _debug_renderer.set_world(_voxel_node->get_world().ptr()); + } +} + void VoxelGraphEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_INTERNAL_PROCESS: @@ -603,12 +640,24 @@ void VoxelGraphEditor::_check_nothing_selected() { } } +void reset_modulates(GraphEdit &graph_edit) { + for (int child_index = 0; child_index < graph_edit.get_child_count(); ++child_index) { + VoxelGraphEditorNode *node_view = + Object::cast_to(graph_edit.get_child(child_index)); + if (node_view == nullptr) { + continue; + } + node_view->set_modulate(Color(1, 1, 1)); + } +} + void VoxelGraphEditor::update_previews() { if (_graph.is_null()) { return; } clear_range_analysis_tooltips(); + reset_modulates(*_graph_edit); uint64_t time_before = OS::get_singleton()->get_ticks_usec(); @@ -640,11 +689,12 @@ void VoxelGraphEditor::update_range_analysis_previews() { ERR_FAIL_COND(!_graph->is_good()); const AABB aabb = _range_analysis_dialog->get_aabb(); - - _graph->analyze_range(aabb.position, aabb.position + aabb.size); + _graph->analyze_range(aabb.position, aabb.position + aabb.size, true, true); const VoxelGraphRuntime::State &state = _graph->get_last_state_from_current_thread(); + const Color greyed_out_color(1, 1, 1, 0.5); + for (int child_index = 0; child_index < _graph_edit->get_child_count(); ++child_index) { VoxelGraphEditorNode *node_view = Object::cast_to(_graph_edit->get_child(child_index)); @@ -652,6 +702,14 @@ void VoxelGraphEditor::update_range_analysis_previews() { continue; } + if (node_view->output_labels.size() == 0) { + continue; + } + + // Assume the node won't run for now + // TODO Would be nice if GraphEdit's minimap would take such coloring into account... + node_view->set_modulate(greyed_out_color); + for (int port_index = 0; port_index < node_view->output_labels.size(); ++port_index) { ProgramGraph::PortLocation loc; loc.node_id = node_view->node_id; @@ -665,6 +723,34 @@ void VoxelGraphEditor::update_range_analysis_previews() { label->set_tooltip(String("Min: {0}\nMax: {1}").format(varray(range.min, range.max))); } } + + // Highlight only nodes that will actually run + ArraySlice execution_map = state.get_debug_execution_map(); + for (unsigned int i = 0; i < execution_map.size(); ++i) { + String node_view_path = node_to_gui_name(execution_map[i]); + VoxelGraphEditorNode *node_view = + Object::cast_to(_graph_edit->get_node(node_view_path)); + node_view->set_modulate(Color(1, 1, 1)); + } +} + +void VoxelGraphEditor::update_range_analysis_gizmo() { + if (!_range_analysis_dialog->is_analysis_enabled()) { + _debug_renderer.clear(); + return; + } + + if (_voxel_node == nullptr) { + return; + } + + const Transform parent_transform = _voxel_node->get_global_transform(); + const AABB aabb = _range_analysis_dialog->get_aabb(); + _debug_renderer.begin(); + _debug_renderer.draw_box( + parent_transform * Transform(Basis().scaled(aabb.size), aabb.position), + VoxelDebug::ID_VOXEL_GRAPH_DEBUG_BOUNDS); + _debug_renderer.end(); } void VoxelGraphEditor::update_slice_previews() { @@ -819,6 +905,16 @@ void VoxelGraphEditor::_on_analyze_range_button_pressed() { _range_analysis_dialog->popup_centered_minsize(); } +void VoxelGraphEditor::_on_range_analysis_toggled(bool enabled) { + schedule_preview_update(); + update_range_analysis_gizmo(); +} + +void VoxelGraphEditor::_on_range_analysis_area_changed() { + schedule_preview_update(); + update_range_analysis_gizmo(); +} + void VoxelGraphEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_on_graph_edit_gui_input", "event"), &VoxelGraphEditor::_on_graph_edit_gui_input); ClassDB::bind_method( @@ -841,6 +937,10 @@ void VoxelGraphEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_on_profile_button_pressed"), &VoxelGraphEditor::_on_profile_button_pressed); ClassDB::bind_method(D_METHOD("_on_analyze_range_button_pressed"), &VoxelGraphEditor::_on_analyze_range_button_pressed); + ClassDB::bind_method(D_METHOD("_on_range_analysis_toggled", "enabled"), + &VoxelGraphEditor::_on_range_analysis_toggled); + ClassDB::bind_method(D_METHOD("_on_range_analysis_area_changed"), + &VoxelGraphEditor::_on_range_analysis_area_changed); ClassDB::bind_method(D_METHOD("_check_nothing_selected"), &VoxelGraphEditor::_check_nothing_selected); diff --git a/editor/graph/voxel_graph_editor.h b/editor/graph/voxel_graph_editor.h index 06503099..5e9ed85c 100644 --- a/editor/graph/voxel_graph_editor.h +++ b/editor/graph/voxel_graph_editor.h @@ -1,6 +1,7 @@ #ifndef VOXEL_GRAPH_EDITOR_H #define VOXEL_GRAPH_EDITOR_H +#include "../voxel_debug.h" #include class VoxelGeneratorGraph; @@ -9,6 +10,8 @@ class PopupMenu; class AcceptDialog; class UndoRedo; class VoxelRangeAnalysisDialog; +class VoxelNode; +class Spatial; class VoxelGraphEditor : public Control { GDCLASS(VoxelGraphEditor, Control) @@ -22,6 +25,7 @@ public: inline Ref get_graph() const { return _graph; } void set_undo_redo(UndoRedo *undo_redo); + void set_voxel_node(VoxelNode *node); private: void _notification(int p_what); @@ -37,6 +41,7 @@ private: void update_previews(); void update_slice_previews(); void update_range_analysis_previews(); + void update_range_analysis_gizmo(); void clear_range_analysis_tooltips(); void _on_graph_edit_gui_input(Ref event); @@ -52,6 +57,8 @@ private: void _on_graph_changed(); void _on_graph_node_name_changed(int node_id); void _on_analyze_range_button_pressed(); + void _on_range_analysis_toggled(bool enabled); + void _on_range_analysis_area_changed(); void _check_nothing_selected(); @@ -67,6 +74,8 @@ private: Vector2 _click_position; bool _nothing_selected_check_scheduled = false; float _time_before_preview_update = 0.f; + Spatial *_voxel_node = nullptr; + VoxelDebug::DebugRenderer _debug_renderer; }; #endif // VOXEL_GRAPH_EDITOR_H diff --git a/editor/graph/voxel_graph_editor_plugin.cpp b/editor/graph/voxel_graph_editor_plugin.cpp index cbca0d84..c73ba3ad 100644 --- a/editor/graph/voxel_graph_editor_plugin.cpp +++ b/editor/graph/voxel_graph_editor_plugin.cpp @@ -1,8 +1,10 @@ #include "voxel_graph_editor_plugin.h" #include "../../generators/graph/voxel_generator_graph.h" +#include "../../terrain/voxel_node.h" #include "voxel_graph_editor.h" #include "voxel_graph_node_inspector_wrapper.h" +#include #include VoxelGraphEditorPlugin::VoxelGraphEditorPlugin(EditorNode *p_node) { @@ -41,6 +43,19 @@ void VoxelGraphEditorPlugin::edit(Object *p_object) { Ref graph(graph_ptr); _graph_editor->set_undo_redo(&get_undo_redo()); // UndoRedo isn't available in constructor _graph_editor->set_graph(graph); + + VoxelNode *voxel_node = nullptr; + Array selected_nodes = get_editor_interface()->get_selection()->get_selected_nodes(); + for (int i = 0; i < selected_nodes.size(); ++i) { + Node *node = selected_nodes[i]; + ERR_FAIL_COND(node == nullptr); + VoxelNode *vn = Object::cast_to(node); + if (vn != nullptr && vn->get_generator() == graph_ptr) { + voxel_node = vn; + break; + } + } + _graph_editor->set_voxel_node(voxel_node); } void VoxelGraphEditorPlugin::make_visible(bool visible) { @@ -50,6 +65,7 @@ void VoxelGraphEditorPlugin::make_visible(bool visible) { } else { _bottom_panel_button->hide(); + _graph_editor->set_voxel_node(nullptr); // TODO Awful hack to handle the nonsense happening in `_on_graph_editor_node_selected` if (!_deferred_visibility_scheduled) { diff --git a/editor/voxel_debug.cpp b/editor/voxel_debug.cpp index d0cb2981..72b73672 100644 --- a/editor/voxel_debug.cpp +++ b/editor/voxel_debug.cpp @@ -21,6 +21,8 @@ static Color get_color(ColorID id) { return Color(1, 1, 1); case ID_OCTREE_BOUNDS: return Color(0.5, 0.5, 0.5); + case ID_VOXEL_GRAPH_DEBUG_BOUNDS: + return Color(1.0, 1.0, 0.0); default: CRASH_NOW_MSG("Unexpected index"); } @@ -170,6 +172,7 @@ void DebugRenderer::begin() { } void DebugRenderer::draw_box(Transform t, ColorID color) { + // Pick an existing item, or create one DebugRendererItem *item; if (_current >= _items.size()) { item = memnew(DebugRendererItem); @@ -188,6 +191,7 @@ void DebugRenderer::draw_box(Transform t, ColorID color) { void DebugRenderer::end() { CRASH_COND(!_inside_block); + // Hide exceeding items for (unsigned int i = _current; i < _items.size(); ++i) { DebugRendererItem *item = _items[i]; item->set_visible(false); diff --git a/editor/voxel_debug.h b/editor/voxel_debug.h index 7eae2a8e..e963002c 100644 --- a/editor/voxel_debug.h +++ b/editor/voxel_debug.h @@ -13,6 +13,7 @@ namespace VoxelDebug { enum ColorID { ID_VOXEL_BOUNDS = 0, ID_OCTREE_BOUNDS, + ID_VOXEL_GRAPH_DEBUG_BOUNDS, ID_COUNT }; diff --git a/generators/graph/program_graph.cpp b/generators/graph/program_graph.cpp index 5ed021cc..d8411942 100644 --- a/generators/graph/program_graph.cpp +++ b/generators/graph/program_graph.cpp @@ -207,7 +207,7 @@ void ProgramGraph::find_terminal_nodes(std::vector &node_ids) const { } } -void ProgramGraph::find_dependencies(std::vector nodes_to_process, std::vector &order) const { +void ProgramGraph::find_dependencies(std::vector nodes_to_process, std::vector &out_order) const { // Finds dependencies of the given nodes, and returns them in the order they should be processed std::unordered_set visited_nodes; @@ -233,7 +233,7 @@ void ProgramGraph::find_dependencies(std::vector nodes_to_process, std if (nodes_to_process_begin == nodes_to_process.size()) { // No ancestor to visit, process the node - order.push_back(node->id); + out_order.push_back(node->id); visited_nodes.insert(node->id); nodes_to_process.pop_back(); } diff --git a/generators/graph/program_graph.h b/generators/graph/program_graph.h index 1d3742d1..0821727d 100644 --- a/generators/graph/program_graph.h +++ b/generators/graph/program_graph.h @@ -60,7 +60,7 @@ public: bool disconnect(PortLocation src, PortLocation dst); bool has_path(uint32_t p_src_node_id, uint32_t p_dst_node_id) const; - void find_dependencies(std::vector nodes_to_process, std::vector &order) const; + void find_dependencies(std::vector nodes_to_process, std::vector &out_order) const; void find_immediate_dependencies(uint32_t node_id, std::vector &deps) const; void find_depth_first(uint32_t start_node_id, std::vector &order) const; void find_terminal_nodes(std::vector &node_ids) const; diff --git a/generators/graph/voxel_generator_graph.cpp b/generators/graph/voxel_generator_graph.cpp index 0d47a639..7101251b 100644 --- a/generators/graph/voxel_generator_graph.cpp +++ b/generators/graph/voxel_generator_graph.cpp @@ -270,6 +270,9 @@ void VoxelGeneratorGraph::generate_block(VoxelBlockRequest &input) { // The section may have the surface in it, we have to calculate it + // Optimize out branches of the graph that won't contribute to the result + runtime->generate_optimized_execution_map(cache.state, false); + { unsigned int i = 0; for (int rz = rmin.z, gz = gmin.z; rz < rmax.z; ++rz, gz += stride) { @@ -286,7 +289,7 @@ void VoxelGeneratorGraph::generate_block(VoxelBlockRequest &input) { y_cache.fill(gy); - runtime->generate_set(cache.state, x_cache, y_cache, z_cache, slice_cache, ry != rmin.y); + runtime->generate_set(cache.state, x_cache, y_cache, z_cache, slice_cache, ry != rmin.y, true); // TODO Flatten this further { @@ -332,7 +335,7 @@ void VoxelGeneratorGraph::generate_set(ArraySlice in_x, ArraySlice ERR_FAIL_COND(_runtime == nullptr || !_runtime->has_output()); Cache &cache = _cache; _runtime->prepare_state(cache.state, in_x.size()); - _runtime->generate_set(cache.state, in_x, in_y, in_z, out_sdf, false); + _runtime->generate_set(cache.state, in_x, in_y, in_z, out_sdf, false, false); } const VoxelGraphRuntime::State &VoxelGeneratorGraph::get_last_state_from_current_thread() { @@ -348,6 +351,12 @@ bool VoxelGeneratorGraph::try_get_output_port_address(ProgramGraph::PortLocation return res; } +void VoxelGeneratorGraph::find_dependencies(uint32_t node_id, std::vector &out_dependencies) const { + std::vector dst; + dst.push_back(node_id); + _graph.find_dependencies(dst, out_dependencies); +} + inline Vector3 get_3d_pos_from_panorama_uv(Vector2 uv) { const float xa = -Math_TAU * uv.x - Math_PI; const float ya = -Math_PI * (uv.y - 0.5f); @@ -444,7 +453,7 @@ void VoxelGeneratorGraph::bake_sphere_bumpmap(Ref im, float ref_radius, f } runtime->generate_set(state, - to_slice(x_coords), to_slice(y_coords), to_slice(z_coords), to_slice(sdf_values), false); + to_slice(x_coords), to_slice(y_coords), to_slice(z_coords), to_slice(sdf_values), false, false); // Calculate final pixels im->lock(); @@ -541,8 +550,9 @@ void VoxelGeneratorGraph::bake_sphere_normalmap(Ref im, float ref_radius, ++i; } } + // TODO Perform range analysis on the range of coordinates, it might still yield performance benefits runtime->generate_set(state, - to_slice(x_coords), to_slice(y_coords), to_slice(z_coords), to_slice(sdf_values_p), false); + to_slice(x_coords), to_slice(y_coords), to_slice(z_coords), to_slice(sdf_values_p), false, false); // Get neighbors along X i = 0; @@ -557,7 +567,7 @@ void VoxelGeneratorGraph::bake_sphere_normalmap(Ref im, float ref_radius, } } runtime->generate_set(state, - to_slice(x_coords), to_slice(y_coords), to_slice(z_coords), to_slice(sdf_values_px), false); + to_slice(x_coords), to_slice(y_coords), to_slice(z_coords), to_slice(sdf_values_px), false, false); // Get neighbors along Y i = 0; @@ -572,7 +582,7 @@ void VoxelGeneratorGraph::bake_sphere_normalmap(Ref im, float ref_radius, } } runtime->generate_set(state, - to_slice(x_coords), to_slice(y_coords), to_slice(z_coords), to_slice(sdf_values_py), false); + to_slice(x_coords), to_slice(y_coords), to_slice(z_coords), to_slice(sdf_values_py), false, false); // TODO This is probably invalid due to the distortion, may need to use another approach. // Compute the 3D normal from gradient, then project it? @@ -626,10 +636,11 @@ float VoxelGeneratorGraph::generate_single(const Vector3i &position) { ERR_FAIL_COND_V(runtime == nullptr || !runtime->has_output(), 0.f); Cache &cache = _cache; runtime->prepare_state(cache.state, 1); - return runtime->generate_single(cache.state, position.to_vec3()); + return runtime->generate_single(cache.state, position.to_vec3(), false); } -Interval VoxelGeneratorGraph::analyze_range(Vector3i min_pos, Vector3i max_pos) const { +Interval VoxelGeneratorGraph::analyze_range(Vector3i min_pos, Vector3i max_pos, + bool optimize_execution_map, bool debug) const { std::shared_ptr runtime; { RWLockRead rlock(_runtime_lock); @@ -639,7 +650,11 @@ Interval VoxelGeneratorGraph::analyze_range(Vector3i min_pos, Vector3i max_pos) Cache &cache = _cache; // Note, buffer size is irrelevant here, because range analysis doesn't use buffers runtime->prepare_state(cache.state, 1); - return runtime->analyze_range(cache.state, min_pos, max_pos); + Interval res = runtime->analyze_range(cache.state, min_pos, max_pos); + if (optimize_execution_map) { + runtime->generate_optimized_execution_map(cache.state, debug); + } + return res; } Ref VoxelGeneratorGraph::duplicate(bool p_subresources) const { @@ -821,7 +836,7 @@ float VoxelGeneratorGraph::debug_measure_microseconds_per_voxel(bool singular) { for (uint32_t z = 0; z < cube_size; ++z) { for (uint32_t y = 0; y < cube_size; ++y) { for (uint32_t x = 0; x < cube_size; ++x) { - runtime->generate_single(cache.state, Vector3i(x, y, z).to_vec3()); + runtime->generate_single(cache.state, Vector3i(x, y, z).to_vec3(), false); } } } @@ -849,7 +864,7 @@ float VoxelGeneratorGraph::debug_measure_microseconds_per_voxel(bool singular) { profiling_clock.restart(); for (uint32_t y = 0; y < cube_size; ++y) { - runtime->generate_set(cache.state, sx, sy, sz, sdst, false); + runtime->generate_set(cache.state, sx, sy, sz, sdst, false, false); } elapsed_us += profiling_clock.restart(); @@ -944,7 +959,7 @@ float VoxelGeneratorGraph::_b_generate_single(Vector3 pos) { } Vector2 VoxelGeneratorGraph::_b_analyze_range(Vector3 min_pos, Vector3 max_pos) const { - const Interval r = analyze_range(Vector3i::from_floored(min_pos), Vector3i::from_floored(max_pos)); + const Interval r = analyze_range(Vector3i::from_floored(min_pos), Vector3i::from_floored(max_pos), false, false); return Vector2(r.min, r.max); } diff --git a/generators/graph/voxel_generator_graph.h b/generators/graph/voxel_generator_graph.h index 3efdc459..7a897538 100644 --- a/generators/graph/voxel_generator_graph.h +++ b/generators/graph/voxel_generator_graph.h @@ -118,13 +118,16 @@ public: void generate_set(ArraySlice in_x, ArraySlice in_y, ArraySlice in_z, ArraySlice out_sdf); - Interval analyze_range(Vector3i min_pos, Vector3i max_pos) const; + Interval analyze_range(Vector3i min_pos, Vector3i max_pos, bool optimize_execution_map, bool debug) const; + void generate_optimized_execution_map(); // Returns state from the last generator used in the current thread static const VoxelGraphRuntime::State &get_last_state_from_current_thread(); bool try_get_output_port_address(ProgramGraph::PortLocation port, uint32_t &out_address) const; + void find_dependencies(uint32_t node_id, std::vector &out_dependencies) const; + // Debug float debug_measure_microseconds_per_voxel(bool singular); diff --git a/generators/graph/voxel_graph_node_db.cpp b/generators/graph/voxel_graph_node_db.cpp index 2c015cdc..60c80ac7 100644 --- a/generators/graph/voxel_graph_node_db.cpp +++ b/generators/graph/voxel_graph_node_db.cpp @@ -601,6 +601,7 @@ VoxelGraphNodeDB::VoxelGraphNodeDB() { t.inputs.push_back(Port("b")); t.inputs.push_back(Port("ratio")); t.outputs.push_back(Port("out")); + // TODO Add a `clamp` parameter? It helps optimization t.process_buffer_func = [](ProcessBufferContext &ctx) { const VoxelGraphRuntime::Buffer &a = ctx.get_input(0); const VoxelGraphRuntime::Buffer &b = ctx.get_input(1); @@ -636,6 +637,15 @@ VoxelGraphNodeDB::VoxelGraphNodeDB() { // Note: if I call this `t` like I use to do with `lerp`, GCC complains it shadows `t` from the outer scope. // Even though this lambda does not capture anything from the outer scope :shrug: const Interval r = ctx.get_input(2); + if (r.is_single_value()) { + if (r.min == 1.f) { + // a will be ignored + ctx.ignore_input(0); + } else if (r.min == 0.f) { + // b will be ignored + ctx.ignore_input(1); + } + } ctx.set_output(0, lerp(a, b, r)); }; } diff --git a/generators/graph/voxel_graph_runtime.cpp b/generators/graph/voxel_graph_runtime.cpp index 6de21fe4..02b63fd6 100644 --- a/generators/graph/voxel_graph_runtime.cpp +++ b/generators/graph/voxel_graph_runtime.cpp @@ -80,6 +80,7 @@ VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGr std::vector order; std::vector terminal_nodes; + std::unordered_map node_id_to_dependency_graph; graph.find_terminal_nodes(terminal_nodes); @@ -162,35 +163,48 @@ VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGr //#endif struct MemoryHelper { - std::vector &bindings; - std::vector &constants; + std::vector &buffer_specs; unsigned int next_address = 0; uint16_t add_binding() { const unsigned int a = next_address; ++next_address; - bindings.push_back(a); + 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; - Constant c; - c.address = a; - c.value = v; - constants.push_back(c); + 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.bindings, _program.constants }; + MemoryHelper mem{ _program.buffer_specs }; // Main inputs X, Y, Z _program.x_input_address = mem.add_binding(); @@ -211,9 +225,19 @@ VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGr CRASH_COND(node->outputs.size() != type.outputs.size()); if (order_index == xzy_start_index) { - _program.xzy_start = operations.size(); + _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_output = 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: { @@ -251,6 +275,18 @@ VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGr // Previous node ports must have been registered CRASH_COND(aptr == nullptr); _program.sdf_output_address = *aptr; + + BufferSpec &bs = _program.buffer_specs[*aptr]; + ++bs.users_count; + + // 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; + dg_node.is_output = true; + _program.sdf_output_node_index = dg_node_index; } continue; @@ -259,7 +295,14 @@ VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGr }; // Add actual operation + CRASH_COND(node->type_id > 0xff); + + if (order_index == xzy_start_index) { + _program.xzy_start_execution_map_index = _program.default_execution_map.size(); + } + _program.default_execution_map.push_back(operations.size()); + append(operations, static_cast(node->type_id)); // Inputs and outputs use a convention so we can have generic code for them. @@ -281,9 +324,19 @@ VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGr // 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; } append(operations, a); + + BufferSpec &bs = _program.buffer_specs[a]; + ++bs.users_count; } // Add outputs @@ -365,13 +418,182 @@ VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGr return result; } -float VoxelGraphRuntime::generate_single(State &state, Vector3 position) const { +static ArraySlice get_outputs_from_op_address( + ArraySlice &operations, uint16_t op_address) { + + const uint8_t opid = operations[op_address]; + const VoxelGraphNodeDB::NodeType &node_type = VoxelGraphNodeDB::get_singleton()->get_type(opid); + + const uint32_t inputs_size = node_type.inputs.size() * sizeof(uint16_t); + const uint32_t outputs_size = node_type.outputs.size() * sizeof(uint16_t); + + // The +1 is for `opid` + return operations.sub(op_address + 1 + inputs_size, outputs_size).reinterpret_cast_to(); +} + +bool VoxelGraphRuntime::is_operation_constant(const State &state, uint16_t op_address) const { + ArraySlice outputs = get_outputs_from_op_address(to_slice_const(_program.operations), op_address); + + for (unsigned int i = 0; i < outputs.size(); ++i) { + const uint16_t output_address = outputs[i]; + const Buffer &buffer = state.get_buffer(output_address); + if (!(buffer.is_constant || + state.get_range(output_address).is_single_value() || + buffer.local_users_count == 0)) { + // At least one of the outputs cannot be predicted in the current area + return false; + } + } + + return true; +} + +// Generates a list of adresses for the operations to execute, +// skipping those that are deemed constant by the last range analysis. +// If a non-constant operation only contributes to a constant one, it will also be skipped. +// This has the effect of optimizing locally at runtime without relying on explicit conditionals. +// It can be useful for biomes, where some branches become constant when not used in the final blending. +void VoxelGraphRuntime::generate_execution_map(const State &state, + std::vector &execution_map, unsigned int &out_mapped_xzy_start, + std::vector *debug_execution_map) const { + + VOXEL_PROFILE_SCOPE(); + + // Range analysis results must have been computed + ERR_FAIL_COND(state.ranges.size() == 0); + + const Program &program = _program; + const DependencyGraph &graph = program.dependency_graph; + + static thread_local std::vector to_process; + to_process.clear(); + to_process.push_back(program.sdf_output_node_index); + + enum ProcessResult { + NOT_PROCESSED, + SKIPPABLE, + REQUIRED + }; + + static thread_local std::vector results; + results.clear(); + results.resize(graph.nodes.size(), NOT_PROCESSED); + + while (to_process.size() != 0) { + const uint32_t node_index = to_process.back(); + const unsigned int to_process_previous_size = to_process.size(); + + // Check needed because Godot never compiles with `_DEBUG`... +#ifdef DEBUG_ENABLED + CRASH_COND(node_index >= graph.nodes.size()); +#endif + const DependencyGraph::Node &node = graph.nodes[node_index]; + + // Ignore outputs because they are not present in the operations list + if (!node.is_output && is_operation_constant(state, node.op_address)) { + // Skip this operation for now. + // If no other dependency reaches it, it will be effectively skipped in the result. + to_process.pop_back(); + results[node_index] = SKIPPABLE; + continue; + } + + for (uint32_t i = node.first_dependency; i < node.end_dependency; ++i) { + const uint32_t dep_node_index = graph.dependencies[i]; + if (results[dep_node_index] != NOT_PROCESSED) { + // Already processed + continue; + } + to_process.push_back(dep_node_index); + } + + if (to_process_previous_size == to_process.size()) { + to_process.pop_back(); + results[node_index] = REQUIRED; + } + } + + if (debug_execution_map != nullptr) { + debug_execution_map->clear(); + for (unsigned int node_index = 0; node_index < graph.nodes.size(); ++node_index) { + const ProcessResult res = results[node_index]; + const DependencyGraph::Node &node = graph.nodes[node_index]; + if (res == REQUIRED) { + debug_execution_map->push_back(node.debug_node_id); + } + } + } + + execution_map.clear(); + + ArraySlice operations(program.operations.data(), 0, program.operations.size()); + bool xzy_start_not_assigned = true; + + // Now we have to fill buffers with the local constants we may have found. + // We iterate nodes primarily because we have to preserve a certain order relative to outer loop optimization. + for (unsigned int node_index = 0; node_index < graph.nodes.size(); ++node_index) { + const ProcessResult res = results[node_index]; + const DependencyGraph::Node &node = graph.nodes[node_index]; + + if (node.is_output) { + continue; + } + + switch (res) { + case NOT_PROCESSED: + continue; + + case SKIPPABLE: { + const ArraySlice outputs = get_outputs_from_op_address(operations, node.op_address); + + for (unsigned int i = 0; i < outputs.size(); ++i) { + const uint16_t output_address = outputs[i]; + const Buffer &buffer = state.get_buffer(output_address); + + if (buffer.is_constant) { + // Already assigned at prepare-time + continue; + } + + CRASH_COND(buffer.is_binding); + + // The node is considered skippable, which means its outputs are either locally constant or unused. + // Unused buffers can be left as-is, but local constants must be filled in. + if (buffer.local_users_count > 0) { + const Interval range = state.ranges[output_address]; + CRASH_COND(!range.is_single_value()); + const float v = range.min; + for (unsigned int j = 0; j < buffer.size; ++j) { + buffer.data[j] = v; + } + } + } + } break; + + case REQUIRED: + if (xzy_start_not_assigned && node.op_address >= program.xzy_start_op_address) { + // This should be correct as long as the list of nodes in the graph follows the same re-ordered + // optimization done in `compile()` such that all nodes not depending on Y come first + out_mapped_xzy_start = execution_map.size(); + xzy_start_not_assigned = false; + } + execution_map.push_back(node.op_address); + break; + + default: + CRASH_NOW(); + break; + } + } +} + +float VoxelGraphRuntime::generate_single(State &state, Vector3 position, bool use_execution_map) const { float output; generate_set(state, ArraySlice(&position.x, 1), ArraySlice(&position.y, 1), ArraySlice(&position.z, 1), - ArraySlice(&output, 1), false); + ArraySlice(&output, 1), false, use_execution_map); return output; } @@ -385,17 +607,21 @@ void VoxelGraphRuntime::prepare_state(State &state, unsigned int buffer_size) co ArraySlice buffers(state.buffers, 0, state.buffers.size()); state.buffer_size = buffer_size; - for (auto it = _program.bindings.begin(); it != _program.bindings.end(); ++it) { - const uint16_t a = *it; - Buffer &b = buffers[a]; - if (b.is_binding) { - // Forgot to unbind? - CRASH_COND(b.data != nullptr); - } else if (b.data != nullptr) { - // Deallocate this buffer if it wasnt a binding and contained something - memdelete(b.data); + for (auto it = _program.buffer_specs.cbegin(); it != _program.buffer_specs.cend(); ++it) { + const BufferSpec &buffer_spec = *it; + Buffer &buffer = buffers[buffer_spec.address]; + + if (buffer_spec.is_binding) { + if (buffer.is_binding) { + // Forgot to unbind? + CRASH_COND(buffer.data != nullptr); + } else if (buffer.data != nullptr) { + // Deallocate this buffer if it wasnt a binding and contained something + memdelete(buffer.data); + } } - b.is_binding = true; + + buffer.is_binding = buffer_spec.is_binding; } // Allocate more buffers if needed @@ -442,19 +668,23 @@ void VoxelGraphRuntime::prepare_state(State &state, unsigned int buffer_size) co state.ranges.resize(_program.buffer_count); // Always reset constants because we don't know if we'll run the same program as before... - for (auto it = _program.constants.begin(); it != _program.constants.end(); ++it) { - const Constant &c = *it; - Buffer &buffer = buffers[c.address]; - buffer.is_constant = true; - buffer.constant_value = c.value; - CRASH_COND(buffer.size > buffer.capacity); - for (unsigned int j = 0; j < buffer_size; ++j) { - buffer.data[j] = c.value; + for (auto it = _program.buffer_specs.cbegin(); it != _program.buffer_specs.cend(); ++it) { + const BufferSpec &bs = *it; + Buffer &buffer = buffers[bs.address]; + if (bs.is_constant) { + buffer.is_constant = true; + buffer.constant_value = bs.constant_value; + CRASH_COND(buffer.size > buffer.capacity); + for (unsigned int j = 0; j < buffer_size; ++j) { + buffer.data[j] = bs.constant_value; + } + CRASH_COND(bs.address >= state.ranges.size()); + state.ranges[bs.address] = Interval::from_single_value(bs.constant_value); } - CRASH_COND(c.address >= state.ranges.size()); - state.ranges[c.address] = Interval::from_single_value(c.value); } + state.execution_map.clear(); + /*if (use_range_analysis) { // TODO To be really worth it, we may need a runtime graph traversal pass, // where we build an execution map of nodes that are worthy 🔨 @@ -471,7 +701,8 @@ void VoxelGraphRuntime::prepare_state(State &state, unsigned int buffer_size) co void VoxelGraphRuntime::generate_set(State &state, ArraySlice in_x, ArraySlice in_y, ArraySlice in_z, - ArraySlice out_sdf, bool skip_xz) const { + ArraySlice out_sdf, bool skip_xz, bool use_execution_map) const { + // I don't like putting private helper functions in headers. struct L { static inline void bind_buffer(ArraySlice buffers, int a, ArraySlice d) { @@ -503,6 +734,8 @@ void VoxelGraphRuntime::generate_set(State &state, ERR_FAIL_COND(state.buffers.size() == 0); ERR_FAIL_COND(state.buffer_size < buffer_size); ERR_FAIL_COND(state.buffers[0].size < buffer_size); + ERR_FAIL_COND(use_execution_map && state.execution_map.size() == 0); + ERR_FAIL_COND(use_execution_map && state.execution_map_xzy_start_index >= state.execution_map.size()); #ifdef DEBUG_ENABLED for (size_t i = 0; i < state.buffers.size(); ++i) { const Buffer &b = state.buffers[i]; @@ -529,19 +762,21 @@ void VoxelGraphRuntime::generate_set(State &state, L::bind_buffer(buffers, _program.z_input_address, in_z); } - uint32_t pc = skip_xz ? _program.xzy_start : 0; - - // STL is unreadable on debug builds of Godot, because _DEBUG isn't defined - //#ifdef DEBUG_ENABLED - // const size_t memory_size = memory.size(); - // const size_t program_size = _program.size(); - // const float *memory_raw = memory.data(); - // const uint8_t *program_raw = (const uint8_t *)_program.data(); - //#endif - const ArraySlice operations(_program.operations.data(), 0, _program.operations.size()); - while (pc < operations.size()) { + ArraySlice execution_map = use_execution_map ? + to_slice_const(state.execution_map) : + to_slice_const(_program.default_execution_map); + if (skip_xz) { + const unsigned int offset = use_execution_map ? + state.execution_map_xzy_start_index : + _program.xzy_start_execution_map_index; + execution_map = execution_map.sub(offset); + } + + for (unsigned int execution_map_index = 0; execution_map_index < execution_map.size(); ++execution_map_index) { + unsigned int pc = execution_map[execution_map_index]; + const uint8_t opid = operations[pc++]; const VoxelGraphNodeDB::NodeType &node_type = VoxelGraphNodeDB::get_singleton()->get_type(opid); @@ -559,18 +794,7 @@ void VoxelGraphRuntime::generate_set(State &state, ArraySlice params; if (params_size > 0) { params = operations.sub(pc, params_size); - pc += params_size; - } - - // TODO May be replaced by execution mapping? - // Skip node if all its outputs are constant - bool all_outputs_constant = true; - for (uint32_t i = 0; i < outputs.size(); ++i) { - const Buffer &buffer = buffers[outputs[i]]; - all_outputs_constant &= buffer.is_constant; - } - if (all_outputs_constant) { - continue; + //pc += params_size; } ERR_FAIL_COND(node_type.process_buffer_func == nullptr); @@ -600,12 +824,22 @@ void VoxelGraphRuntime::generate_set(State &state, // TODO Accept float bounds Interval VoxelGraphRuntime::analyze_range(State &state, Vector3i min_pos, Vector3i max_pos) const { + VOXEL_PROFILE_SCOPE(); + #ifdef TOOLS_ENABLED ERR_FAIL_COND_V_MSG(!has_output(), Interval(), "The graph has no SDF output"); ERR_FAIL_COND_V(state.ranges.size() != _program.buffer_count, Interval()); #endif ArraySlice ranges(state.ranges, 0, state.ranges.size()); + ArraySlice buffers(state.buffers, 0, state.buffers.size()); + + // Reset users count, as they might be decreased during the analysis + for (auto it = _program.buffer_specs.cbegin(); it != _program.buffer_specs.cend(); ++it) { + const BufferSpec &bs = *it; + Buffer &b = buffers[bs.address]; + b.local_users_count = bs.users_count; + } ranges[_program.x_input_address] = Interval(min_pos.x, max_pos.x); ranges[_program.y_input_address] = Interval(min_pos.y, max_pos.y); @@ -613,6 +847,8 @@ Interval VoxelGraphRuntime::analyze_range(State &state, Vector3i min_pos, Vector const ArraySlice operations(_program.operations.data(), 0, _program.operations.size()); + // Here operations must all be analyzed, because we do this as a broad-phase. + // Only narrow-phase may skip some operations eventually. uint32_t pc = 0; while (pc < operations.size()) { const uint8_t opid = operations[pc++]; @@ -636,7 +872,7 @@ Interval VoxelGraphRuntime::analyze_range(State &state, Vector3i min_pos, Vector } ERR_FAIL_COND_V(node_type.range_analysis_func == nullptr, Interval()); - RangeAnalysisContext ctx(inputs, outputs, params, ranges); + RangeAnalysisContext ctx(inputs, outputs, params, ranges, buffers); node_type.range_analysis_func(ctx); #ifdef VOXEL_DEBUG_GRAPH_PROG_SENTINEL diff --git a/generators/graph/voxel_graph_runtime.h b/generators/graph/voxel_graph_runtime.h index 92051f91..e7bfcbb3 100644 --- a/generators/graph/voxel_graph_runtime.h +++ b/generators/graph/voxel_graph_runtime.h @@ -19,6 +19,7 @@ public: }; struct Buffer { + // Values of the buffer. Must contain at least `size` values. // TODO Consider wrapping this in debug mode. It is one of the rare cases I didnt do it. // I spent an hour debugging memory corruption which originated from an overrun while accessing this data. float *data = nullptr; @@ -26,9 +27,15 @@ public: // All buffers have the same available count, size is here only for convenience. unsigned int size; unsigned int capacity; + // Constant value of the buffer, if it is a compile-time constant float constant_value; + // Is the buffer holding a compile-time constant bool is_constant; + // Is the buffer a user input/output bool is_binding = false; + // How many operations are using this buffer as input. + // This value is only relevant when using optimized execution mapping. + unsigned int local_users_count; }; // Contains the data the program will modify while it runs. @@ -60,11 +67,23 @@ public: ranges.clear(); } + ArraySlice get_debug_execution_map() const { + return to_slice_const(debug_execution_map); + } + private: friend class VoxelGraphRuntime; std::vector ranges; std::vector buffers; + + // Stores operation addresses + std::vector execution_map; + + // Stores node IDs referring to the user-facing graph + std::vector debug_execution_map; + + unsigned int execution_map_xzy_start_index; unsigned int buffer_size = 0; unsigned int buffer_capacity = 0; }; @@ -80,13 +99,24 @@ public: // If none of these change, you can keep re-using it. void prepare_state(State &state, unsigned int buffer_size) const; - float generate_single(State &state, Vector3 position) const; + float generate_single(State &state, Vector3 position, bool use_execution_map) const; void generate_set(State &state, ArraySlice in_x, ArraySlice in_y, ArraySlice in_z, - ArraySlice out_sdf, bool skip_xz) const; + ArraySlice out_sdf, bool skip_xz, bool use_execution_map) const; + // Analyzes a specific region of inputs to find out what ranges of outputs we can expect. + // It can be used to speed up calls to `generate_set` thanks to execution mapping, + // so that operations can be optimized out if they don't contribute to the result. Interval analyze_range(State &state, Vector3i min_pos, Vector3i max_pos) const; + // Call this after `analyze_range` if you intend to actually generate a set or single values in the area. + // This allows to use the execution map optimization, until you choose another area. + // (i.e when using this, querying values outside of the analyzed area may be invalid) + inline void generate_optimized_execution_map(State &state, bool debug) const { + generate_execution_map(state, state.execution_map, state.execution_map_xzy_start_index, + debug ? &state.debug_execution_map : nullptr); + } + inline bool has_output() const { return _program.sdf_output_address != -1; } @@ -220,9 +250,11 @@ public: const ArraySlice inputs, const ArraySlice outputs, const ArraySlice params, - ArraySlice ranges) : + ArraySlice ranges, + ArraySlice buffers) : _ProcessContext(inputs, outputs, params), - _ranges(ranges) {} + _ranges(ranges), + _buffers(buffers) {} inline const Interval get_input(uint32_t i) const { const uint32_t address = get_input_address(i); @@ -234,8 +266,15 @@ public: _ranges[address] = r; } + inline void ignore_input(uint32_t i) { + const uint32_t address = get_input_address(i); + Buffer &b = _buffers[address]; + --b.local_users_count; + } + private: ArraySlice _ranges; + ArraySlice _buffers; }; typedef void (*CompileFunc)(CompileContext &); @@ -245,38 +284,118 @@ public: private: CompilationResult _compile(const ProgramGraph &graph, bool debug); - struct Constant { - unsigned int address; - float value = 0; + void VoxelGraphRuntime::generate_execution_map(const State &state, + std::vector &execution_map, unsigned int &out_mapped_xzy_start, + std::vector *debug_execution_map) const; + + bool is_operation_constant(const State &state, uint16_t op_address) const; + + struct BufferSpec { + // Index the buffer should be stored at + uint16_t address; + // How many nodes use this buffer as input + uint16_t users_count; + // Value of the compile-time constant, if any + float constant_value; + // Is the buffer constant at compile time + bool is_constant; + // Is the buffer a user input/output + bool is_binding; }; + struct DependencyGraph { + struct Node { + uint16_t first_dependency; + uint16_t end_dependency; + uint16_t op_address; + bool is_output; + int debug_node_id; + }; + + // Indexes to the `nodes` array + std::vector dependencies; + // Nodes in the same order they would be in the default execution map + std::vector nodes; + + inline void clear() { + dependencies.clear(); + nodes.clear(); + } + }; + + // Precalculated program data. // Remains constant and read-only after compilation. struct Program { + // Serialized operations and arguments. + // They come up as series of . + // They should be laid out in the same order they will be run in, although it's not absolutely required. + // It's better to have it ordered because memory access will be more predictable. std::vector operations; + + // Describes dependencies between operations. It is generated at compile time. + // It is used to perform dynamic optimization in case some operations can be predicted as constant. + DependencyGraph dependency_graph; + + // List of indexes within `operations` describing which order they should be run into by default. + // It's used because sometimes we may want to override with a simplified execution map dynamically. + // When we don't, we use the default one so the code doesn't have to change. + std::vector default_execution_map; + + // Heap-allocated parameters data, when too large to fit in `operations`. + // We keep a reference to them so they can be freed when the program is cleared. std::vector heap_resources; + + // Heap-allocated parameters data, when too large to fit in `operations`. + // We keep a reference to them so they won't be freed until the program is cleared. std::vector > ref_resources; - std::vector constants; - std::vector bindings; - uint32_t xzy_start; + + // Describes the list of buffers to prepare in `State` before the program can be run + std::vector buffer_specs; + + // Address in `operations` from which operations will depend on Y. Operations before never depend on it. + // It is used to optimize away calculations that would otherwise be the same in planar terrain use cases. + uint32_t xzy_start_op_address; + uint32_t xzy_start_execution_map_index; + + // Note: the following buffers are allocated by the user. + // They are mapped temporarily into the same array of buffers inside `State`, + // so we won't need specific code to handle them. This requires knowing at which index they are reserved. + // They must be all assigned for the program to run correctly. + // + // Address within the State's array of buffers where the X input may be. int x_input_address = -1; + // Address within the State's array of buffers where the Y input may be. int y_input_address = -1; + // Address within the State's array of buffers where the Z input may be. int z_input_address = -1; + // Address within the State's array of buffers where the SDF output may be. int sdf_output_address = -1; + int sdf_output_node_index = -1; + + // Maximum amount of buffers this program will need to do a full run. + // Buffers are needed to hold values of arguments and outputs for each operation. unsigned int buffer_count = 0; + + // Associates a high-level port to its corresponding address within the compiled program. + // This is used for debugging intermediate values. HashMap output_port_addresses; + + // Result of the last compilation attempt. The program should not be run if it failed. CompilationResult compilation_result; void clear() { operations.clear(); - constants.clear(); - bindings.clear(); - // Address in the program from which operations will depend on Y. - xzy_start = 0; + buffer_specs.clear(); + xzy_start_execution_map_index = 0; + xzy_start_op_address = 0; + default_execution_map.clear(); output_port_addresses.clear(); + dependency_graph.clear(); sdf_output_address = -1; x_input_address = -1; y_input_address = -1; z_input_address = -1; + sdf_output_node_index = -1; compilation_result = CompilationResult(); for (auto it = heap_resources.begin(); it != heap_resources.end(); ++it) { HeapResource &r = *it; diff --git a/terrain/voxel_node.h b/terrain/voxel_node.h index 4b70c860..9b567e62 100644 --- a/terrain/voxel_node.h +++ b/terrain/voxel_node.h @@ -1,11 +1,11 @@ #ifndef VOXEL_NODE_H #define VOXEL_NODE_H -#include +#include "../generators/voxel_generator.h" +#include "../meshers/voxel_mesher.h" +#include "../streams/voxel_stream.h" -class VoxelMesher; -class VoxelStream; -class VoxelGenerator; +#include // Base class for voxel volumes class VoxelNode : public Spatial {