diff --git a/Cool b/Cool index 4b7fe0d6e..4ad118571 160000 --- a/Cool +++ b/Cool @@ -1 +1 @@ -Subproject commit 4b7fe0d6e3df9c70ed00f7cd660f8db2091c2e5d +Subproject commit 4ad1185714fc03f7f8bb05192ea1d81f0ccc7fb9 diff --git a/Nodes/10 Image/Feedback (One frame delay).clbnode b/Nodes/10 Image/Feedback (One frame delay).clbnode new file mode 100644 index 000000000..6e970c3a0 --- /dev/null +++ b/Nodes/10 Image/Feedback (One frame delay).clbnode @@ -0,0 +1,8 @@ +// To learn how to write nodes, see https://coollab-art.com/Tutorials/Writing%20Nodes/Intro + +INPUT UV->sRGB_StraightA 'Image'; + +sRGB_StraightA main(UV uv) +{ + return vec4(); // Doesn't matter, this node file is just a hack to have a node with the right input and output pins +} \ No newline at end of file diff --git a/Nodes/10 Image/Feedback (Previous frame).clbnode b/Nodes/10 Image/Feedback (Previous frame).clbnode deleted file mode 100644 index b6516d31a..000000000 --- a/Nodes/10 Image/Feedback (Previous frame).clbnode +++ /dev/null @@ -1,7 +0,0 @@ -// To learn how to write nodes, see https://coollab-art.com/Tutorials/Writing%20Nodes/Intro - -sRGB_StraightA main(UV uv) -{ - uv = unnormalize_uv(to_view_space(uv)); - return texture(_previous_frame_texture, uv); -} \ No newline at end of file diff --git a/src/App.cpp b/src/App.cpp index 7693d10fe..235b6251e 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -52,13 +52,13 @@ namespace Lab { App::App(Cool::WindowManager& windows, Cool::ViewsManager& views) : _main_window{windows.main_window()} - , _output_view{views.make_view(Cool::ViewCreationParams{ + , _output_view{views.make_view(Cool::ViewCreationParams{ .name = Cool::icon_fmt("Output", ICOMOON_IMAGE), .is_output_view = true, .is_closable = true, .start_open = false, })} - , _preview_view{views.make_view( + , _preview_view{views.make_view( _output_view, Cool::ViewCreationParams{ .name = Cool::icon_fmt("View", ICOMOON_IMAGE), @@ -116,6 +116,9 @@ void App::update() initial_project_opening(command_execution_context()); } + if (DebugOptions::force_rerender_every_frame()) + _project.modules_graph->request_rerender_all(); + Cool::user_settings().color_themes.update(); _project.audio.set_force_mute(_project.exporter.is_exporting()); @@ -141,7 +144,7 @@ void App::update() if (inputs_are_allowed()) // Must update() before we render() to make sure the modules are ready (e.g. Nodes need to parse the definitions of the nodes from files) { - _nodes_library_manager.update(_project.modules_graph->regenerate_code_flag(), _project.modules_graph->graph(), _project.modules_graph->nodes_config(ui(), _project.audio, _nodes_library_manager.library())); + _nodes_library_manager.update(_project.modules_graph->rebuild_modules_graph_flag(), _project.modules_graph->graph(), _project.modules_graph->nodes_config(ui(), _project.audio, _nodes_library_manager.library())); _project.modules_graph->update(); check_inputs(); } @@ -149,8 +152,8 @@ void App::update() if (!_project.exporter.is_exporting()) { _project.clock.update(); - render_view().update_size(_project.view_constraint); // TODO(JF) Integrate the notion of View Constraint inside the RenderView ? But that's maybe too much coupling - polaroid().render(_project.clock.time(), _project.clock.delta_time()); + auto const render_size = render_view().desired_image_size(_project.view_constraint); // TODO(JF) Integrate the notion of View Constraint inside the TextureView ? But that's may be too much coupling + polaroid().render(render_size, _project.clock.time(), _project.clock.delta_time()); } else { @@ -196,7 +199,7 @@ void App::request_rerender() // TODO(Modules) Sometimes we don't need to call th _project.modules_graph->request_rerender_all(); } -auto App::render_view() -> Cool::RenderView& +auto App::render_view() -> Cool::TextureView& { if (_output_view.is_open()) return _output_view; @@ -206,14 +209,9 @@ auto App::render_view() -> Cool::RenderView& Cool::Polaroid App::polaroid() { return { - .render_target = render_view().render_target(), // TODO(Modules) Each module should have its own render target that it renders on. The views shouldn't have a render target, but receive the one of the top-most module by reference. - .render_fn = [this](Cool::RenderTarget& render_target, Cool::Time time, Cool::Time delta_time) { - if (_last_time != time) - { - _last_time = time; - on_time_changed(); - } - render(render_target, time, delta_time); + .texture = [this]() { return _project.modules_graph->final_texture(); }, + .render = [this](img::Size size, Cool::Time time, Cool::Time delta_time) { + render(size, time, delta_time); } }; } @@ -236,11 +234,15 @@ static void imgui_window_console() #endif } -void App::render(Cool::RenderTarget& render_target, Cool::Time time, Cool::Time delta_time) +void App::render(img::Size size, Cool::Time time, Cool::Time delta_time) { + if (_last_time != time) + { + _last_time = time; + on_time_changed(); + } _project.modules_graph->render( - render_target, - data_to_pass_to_shader(render_target.desired_size(), time, delta_time), + data_to_pass_to_shader(size, time, delta_time), data_to_generate_shader_code() ); } @@ -280,10 +282,12 @@ void App::imgui_window_view() } _project.modules_graph->submit_gizmos(_preview_view.gizmos_manager(), command_executor(), _project.camera_2D_manager.camera()); + _output_view.set_texture(_project.modules_graph->final_texture()); _output_view.imgui_window({ .on_open = [&]() { request_rerender(); }, // When we switch between using the _output_view and the _nodes_view .on_close = [&]() { request_rerender(); }, // as our render target, we need to rerender. }); + _preview_view.set_texture(_project.modules_graph->final_texture()); _preview_view.imgui_window({ .fullscreen = view_in_fullscreen, .extra_widgets = [&]() { @@ -354,7 +358,7 @@ void App::imgui_window_meshing() { _meshing_gui.imgui_window( _mesh_export_settings, - data_to_pass_to_shader(render_view().render_target().current_size(), _project.clock.time(), _project.clock.delta_time()), + data_to_pass_to_shader(render_view().desired_image_size(_project.view_constraint), _project.clock.time(), _project.clock.delta_time()), data_to_generate_shader_code() ); } @@ -405,8 +409,8 @@ void App::imgui_windows_only_when_inputs_are_allowed() // Share online _gallery_poster.imgui_window([&](img::Size size) { auto the_polaroid = polaroid(); - the_polaroid.render(_project.clock.time(), _project.clock.delta_time(), size); - auto const image = the_polaroid.render_target.download_pixels(); + the_polaroid.render(size, _project.clock.time(), _project.clock.delta_time()); + auto const image = the_polaroid.texture().download_pixels(); return img::save_png_to_string(image); }); // Recently opened projects diff --git a/src/App.h b/src/App.h index b83fedd9d..31052d28c 100644 --- a/src/App.h +++ b/src/App.h @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -35,8 +34,8 @@ #include "Cool/StrongTypes/Camera2D.h" #include "Cool/Time/Clock_Realtime.h" #include "Cool/Tips/TipsManager.h" -#include "Cool/View/ForwardingOrRenderView.h" -#include "Cool/View/RenderView.h" +#include "Cool/View/ForwardingOrTextureView.hpp" +#include "Cool/View/TextureView.hpp" #include "Cool/View/ViewsManager.h" #include "Cool/Webcam/WebcamsConfigs.h" #include "Cool/Window/WindowManager.h" @@ -78,10 +77,10 @@ class App : public Cool::IApp { auto nodes_library() const -> Cool::NodesLibrary const& { return _nodes_library_manager.library(); } private: - void render(Cool::RenderTarget& render_target, Cool::Time time, Cool::Time delta_time); + void render(img::Size size, Cool::Time time, Cool::Time delta_time); void on_time_changed(); void on_time_reset(); - auto render_view() -> Cool::RenderView&; + auto render_view() -> Cool::TextureView&; void check_inputs(); void check_inputs__history(); @@ -97,7 +96,7 @@ class App : public Cool::IApp { auto command_executor () { return CommandExecutor{command_execution_context()}; } auto system_values (img::Size render_target_size, Cool::Time time, Cool::Time delta_time) const { return SystemValues{render_target_size, time, delta_time, _project.camera_2D_manager.camera(), _project.camera_3D_manager.camera(), _project.audio}; } auto ui () { return Ui_Ref{command_executor()}; } - auto data_to_pass_to_shader (img::Size render_target_size, Cool::Time time, Cool::Time delta_time) const { return DataToPassToShader{system_values(render_target_size, time, delta_time), _project.modules_graph->graph(), _project.modules_graph->compositing_module().feedback_double_buffer() }; } + auto data_to_pass_to_shader (img::Size render_target_size, Cool::Time time, Cool::Time delta_time) const { return DataToPassToShader{system_values(render_target_size, time, delta_time), _project.modules_graph->graph() }; } auto data_to_generate_shader_code () const { return DataToGenerateShaderCode{_project.modules_graph->graph(), Cool::GetNodeDefinition_Ref{_nodes_library_manager.library()} }; } // clang-format on @@ -123,8 +122,8 @@ class App : public Cool::IApp { private: Cool::Window& _main_window; - Cool::RenderView& _output_view; - Cool::ForwardingOrRenderView& _preview_view; // Must be after _output_view because it stores a reference to it + Cool::TextureView& _output_view; + Cool::ForwardingOrTextureView& _preview_view; // Must be after _output_view because it stores a reference to it Project _project{}; std::optional _current_project_path{}; RecentlyOpened _recently_opened_projects{}; diff --git a/src/Debug/generate_debug_options.py b/src/Debug/generate_debug_options.py index 95ae8ae00..ca25ba5c0 100644 --- a/src/Debug/generate_debug_options.py +++ b/src/Debug/generate_debug_options.py @@ -62,6 +62,11 @@ def all_debug_options(): name_in_ui="Show nodes and links registries", available_in_release=True, ), + DebugOption( + name_in_code="force_rerender_every_frame", + name_in_ui="Force rerender every frame", + available_in_release=True, + ), DebugOption( name_in_code="log_when_rendering", name_in_ui="Log when rendering", diff --git a/src/Debug/generated/DebugOptions.inl b/src/Debug/generated/DebugOptions.inl index 7bddab2d8..2b45b20df 100644 --- a/src/Debug/generated/DebugOptions.inl +++ b/src/Debug/generated/DebugOptions.inl @@ -39,6 +39,7 @@ public: } } [[nodiscard]] static auto show_nodes_and_links_registries() -> bool& { return instance().show_nodes_and_links_registries; } + [[nodiscard]] static auto force_rerender_every_frame() -> bool& { return instance().force_rerender_every_frame; } [[nodiscard]] static auto log_when_rendering() -> bool& { return instance().log_when_rendering; } [[nodiscard]] static auto log_when_updating_particles() -> bool& { return instance().log_when_updating_particles; } [[nodiscard]] static auto log_when_compiling_nodes() -> bool& { return instance().log_when_compiling_nodes; } @@ -90,6 +91,7 @@ private: bool show_imgui_demo_window{false}; bool show_history_window{false}; bool show_nodes_and_links_registries{false}; + bool force_rerender_every_frame{false}; bool log_when_rendering{false}; bool log_when_updating_particles{false}; bool log_when_compiling_nodes{false}; @@ -113,6 +115,7 @@ private: ser20::make_nvp("ImGui Demo window", show_imgui_demo_window), ser20::make_nvp("Show history", show_history_window), ser20::make_nvp("Show nodes and links registries", show_nodes_and_links_registries), + ser20::make_nvp("Force rerender every frame", force_rerender_every_frame), ser20::make_nvp("Log when rendering", log_when_rendering), ser20::make_nvp("Log when updating particles", log_when_updating_particles), ser20::make_nvp("Log when compiling nodes", log_when_compiling_nodes), @@ -128,6 +131,7 @@ private: ser20::make_nvp("ImGui Demo window", show_imgui_demo_window), ser20::make_nvp("Show history", show_history_window), ser20::make_nvp("Show nodes and links registries", show_nodes_and_links_registries), + ser20::make_nvp("Force rerender every frame", force_rerender_every_frame), ser20::make_nvp("Log when rendering", log_when_rendering), ser20::make_nvp("Log when updating particles", log_when_updating_particles), ser20::make_nvp("Log when compiling nodes", log_when_compiling_nodes), @@ -150,6 +154,7 @@ private: instance().show_imgui_demo_window = false; instance().show_history_window = false; instance().show_nodes_and_links_registries = false; + instance().force_rerender_every_frame = false; instance().log_when_rendering = false; instance().log_when_updating_particles = false; instance().log_when_compiling_nodes = false; @@ -218,6 +223,11 @@ private: Cool::ImGuiExtras::toggle("Show nodes and links registries", &instance().show_nodes_and_links_registries); } + if (wafl::similarity_match({filter, "Force rerender every frame"}) >= wafl::Matches::Strongly) + { + Cool::ImGuiExtras::toggle("Force rerender every frame", &instance().force_rerender_every_frame); + } + if (wafl::similarity_match({filter, "Log when rendering"}) >= wafl::Matches::Strongly) { Cool::ImGuiExtras::toggle("Log when rendering", &instance().log_when_rendering); @@ -308,6 +318,12 @@ private: throw 0.f; // To understand why we need to throw, see `toggle_first_option()` in } + if (wafl::similarity_match({filter, "Force rerender every frame"}) >= wafl::Matches::Strongly) + { + instance().force_rerender_every_frame = !instance().force_rerender_every_frame; + throw 0.f; // To understand why we need to throw, see `toggle_first_option()` in + } + if (wafl::similarity_match({filter, "Log when rendering"}) >= wafl::Matches::Strongly) { instance().log_when_rendering = !instance().log_when_rendering; diff --git a/src/Meshing/MeshingGui.cpp b/src/Meshing/MeshingGui.cpp index 450e180fd..57e47f619 100644 --- a/src/Meshing/MeshingGui.cpp +++ b/src/Meshing/MeshingGui.cpp @@ -26,7 +26,7 @@ static void gen_and_export_mesh(Cool::NodeId const& main_node_id, MeshingSetting // auto const node_def = data_to_generate_shader_code.get_node_definition(maybe_node->id_names()); // is_shape_3D(node_def->signature()); - auto const maybe_mesh = gen_mesh_from_sdf(main_node_id, meshing_settings, data_to_pass_to_shader, data_to_generate_shader_code); + auto const maybe_mesh = gen_mesh_from_sdf(main_node_id, meshing_settings, data_to_pass_to_shader, data_to_generate_shader_code, {}); // TODO(Meshing) We need to generate an entire ModulesGraph, so that we can properly handle the fact that our module might depend on other modules if (!maybe_mesh) return; // TODO(Meshing) Error message should be handled here export_mesh(*maybe_mesh, mesh_export_settings); diff --git a/src/Meshing/gen_mesh_from_sdf.cpp b/src/Meshing/gen_mesh_from_sdf.cpp index 13c557e3e..fedca27f8 100644 --- a/src/Meshing/gen_mesh_from_sdf.cpp +++ b/src/Meshing/gen_mesh_from_sdf.cpp @@ -83,10 +83,11 @@ void cool_main() } auto gen_mesh_from_sdf( - Cool::NodeId const& main_node_id, - MeshingSettings const& meshing_settings, - DataToPassToShader const& data_to_pass_to_shader, - DataToGenerateShaderCode const& data_to_generate_shader_code + Cool::NodeId const& main_node_id, + MeshingSettings const& meshing_settings, + DataToPassToShader const& data_to_pass_to_shader, + DataToGenerateShaderCode const& data_to_generate_shader_code, + std::vector> const& module_dependencies ) -> std::optional { if constexpr (COOL_OPENGL_VERSION < 430) @@ -122,10 +123,13 @@ auto gen_mesh_from_sdf( meshing_compute_shader->set_uniform("_step_size", meshing_settings.step_size().x); { - auto dependencies = ModuleDependencies{}; + auto dependencies = ModuleDependencies{}; + auto nodes_that_we_depend_on = std::vector{}; // TODO(Meshing) Properly compute the nodes that we depend on + for (auto const& [id, _] : data_to_pass_to_shader.nodes_graph.nodes()) + nodes_that_we_depend_on.emplace_back(id); update_dependencies_from_shader_code(dependencies, *shader_code); - update_dependencies_from_nodes_graph(dependencies, data_to_pass_to_shader.nodes_graph); - set_uniforms_for_shader_based_module(*meshing_compute_shader, dependencies, data_to_pass_to_shader); + update_dependencies_from_nodes_graph(dependencies, data_to_pass_to_shader.nodes_graph, nodes_that_we_depend_on); + set_uniforms_for_shader_based_module(*meshing_compute_shader, dependencies, data_to_pass_to_shader, module_dependencies, nodes_that_we_depend_on); } meshing_compute_shader->compute(meshing_settings.samples_count); diff --git a/src/Meshing/gen_mesh_from_sdf.hpp b/src/Meshing/gen_mesh_from_sdf.hpp index 0f32ff3a0..d2dd70b8e 100644 --- a/src/Meshing/gen_mesh_from_sdf.hpp +++ b/src/Meshing/gen_mesh_from_sdf.hpp @@ -7,7 +7,15 @@ namespace Lab { +class Module; + // TODO(Meshing) Return error message in case of failure (tl::expected) -auto gen_mesh_from_sdf(Cool::NodeId const& main_node_id, MeshingSettings const&, DataToPassToShader const&, DataToGenerateShaderCode const&) -> std::optional; +auto gen_mesh_from_sdf( + Cool::NodeId const& main_node_id, + MeshingSettings const&, + DataToPassToShader const&, + DataToGenerateShaderCode const&, + std::vector> const& module_dependencies +) -> std::optional; } // namespace Lab \ No newline at end of file diff --git a/src/Module/Module.cpp b/src/Module/Module.cpp index d2aa9c6b6..c2fd0989a 100644 --- a/src/Module/Module.cpp +++ b/src/Module/Module.cpp @@ -13,4 +13,11 @@ void Module::log_module_error(Cool::OptionalErrorMessage const& maybe_err, Cool: }); } +auto Module::needs_to_rerender() const -> bool +{ + return _needs_to_rerender_flag.is_dirty() || std::any_of(_modules_that_we_depend_on.begin(), _modules_that_we_depend_on.end(), [&](auto&& module) { + return module->needs_to_rerender(); + }); +}; + } // namespace Lab \ No newline at end of file diff --git a/src/Module/Module.h b/src/Module/Module.h index fb14ed22a..588706754 100644 --- a/src/Module/Module.h +++ b/src/Module/Module.h @@ -1,6 +1,9 @@ #pragma once #include +#include #include +#include "Cool/Gpu/OpenGL/TextureRef.hpp" +#include "Cool/Gpu/RenderTarget.h" #include "Dependencies/Ui.h" #include "ShaderBased/DataToPassToShader.hpp" @@ -13,21 +16,21 @@ namespace Lab { class Module { public: - Module() = default; - Module(Module const&) = delete; - auto operator=(Module const&) -> Module& = delete; - -protected: + Module() = default; // TODO(Module) remove? + Module(Module const&) = delete; + auto operator=(Module const&) -> Module& = delete; Module(Module&&) noexcept = default; auto operator=(Module&&) noexcept -> Module& = default; + virtual ~Module() = default; -public: - virtual ~Module() = default; - - explicit Module(std::string_view name) + explicit Module(std::string_view name, std::string texture_name_in_shader, std::vector> modules_that_we_depend_on, std::vector nodes_that_we_depend_on) : _name{name} - { - } + , _texture_name_in_shader{std::move(texture_name_in_shader)} + , _modules_that_we_depend_on{std::move(modules_that_we_depend_on)} + , _nodes_that_we_depend_on{std::move(nodes_that_we_depend_on)} + {} + + Cool::NodesGraph const* _nodes_graph{}; // TODO(Particles) Remove [[nodiscard]] auto name() const -> const std::string& { return _name; } @@ -36,25 +39,39 @@ class Module { render(data); _needs_to_rerender_flag.set_clean(); } - virtual void imgui_windows(Ui_Ref) const = 0; /// The ui() method should be const, because it should only trigger commands, not modify internal values (allows us to handle history / re-rendering at a higher level). If you really need to mutate one of your member variables, mark it as `mutable`. - virtual void update() {}; - - [[nodiscard]] virtual auto needs_to_rerender() const -> bool - { - return _needs_to_rerender_flag.is_dirty(); - }; + virtual void imgui_windows(Ui_Ref) const {}; /// The ui() method should be const, because it should only trigger commands, not modify internal values (allows us to handle history / re-rendering at a higher level). If you really need to mutate one of your member variables, mark it as `mutable`. + virtual void update() {}; + virtual void on_time_changed() {}; + virtual void on_time_reset() {}; + virtual void imgui_generated_shader_code_tab() {}; + [[nodiscard]] virtual auto needs_to_rerender() const -> bool; + virtual void before_module_graph_renders() {}; + virtual auto texture() const -> Cool::TextureRef { return _render_target.texture_ref(); } [[nodiscard]] auto needs_to_rerender_flag() const -> Cool::DirtyFlag const& { return _needs_to_rerender_flag; } - -protected: - void log_module_error(Cool::OptionalErrorMessage const&, Cool::MessageSender&) const; + void update_dependencies_from_nodes_graph(Cool::NodesGraph const& graph) { Lab::update_dependencies_from_nodes_graph(_depends_on, graph, _nodes_that_we_depend_on); } + void log_module_error(Cool::OptionalErrorMessage const&, Cool::MessageSender&) const; + auto render_target() -> Cool::RenderTarget& { return _render_target; } + auto render_target() const -> Cool::RenderTarget const& { return _render_target; } + [[nodiscard]] auto depends_on() const -> ModuleDependencies const& { return _depends_on; } + [[nodiscard]] auto texture_name_in_shader() const -> std::string const& { return _texture_name_in_shader; } + [[nodiscard]] auto modules_that_we_depend_on() const -> std::vector> const& { return _modules_that_we_depend_on; } + [[nodiscard]] auto nodes_that_we_depend_on() const -> std::vector const& { return _nodes_that_we_depend_on; } private: virtual void render(DataToPassToShader const&) = 0; private: - std::string _name; - Cool::DirtyFlag _needs_to_rerender_flag; + std::string _name; + Cool::DirtyFlag _needs_to_rerender_flag; + Cool::RenderTarget _render_target{}; + + std::string _texture_name_in_shader{}; + std::vector> _modules_that_we_depend_on{}; + std::vector _nodes_that_we_depend_on{}; + +protected: + ModuleDependencies _depends_on{}; private: friend class ser20::access; diff --git a/src/Module/ModuleDependencies.cpp b/src/Module/ModuleDependencies.cpp index bca68a4e8..e2efc89ea 100644 --- a/src/Module/ModuleDependencies.cpp +++ b/src/Module/ModuleDependencies.cpp @@ -1,4 +1,5 @@ #include "ModuleDependencies.h" +#include "Cool/Nodes/NodeId.h" #include "Cool/String/String.h" #include "Cool/TextureSource/TextureDescriptor.h" #include "Nodes/Node.h" @@ -19,8 +20,7 @@ void update_dependencies_from_shader_code(ModuleDependencies& dependencies, std: { shader_code = Cool::String::remove_comments(shader_code); - dependencies.time |= contains_two_or_more("_time", shader_code) - || contains_two_or_more("_previous_frame_texture", shader_code); + dependencies.time |= contains_two_or_more("_time", shader_code); dependencies.last_midi_button_pressed |= contains_two_or_more("_last_midi_button_pressed", shader_code) || contains_two_or_more("_last_last_midi_button_pressed", shader_code); dependencies.time_since_last_midi_button_pressed |= contains_two_or_more("_time_since_last_midi_button_pressed", shader_code); @@ -29,31 +29,35 @@ void update_dependencies_from_shader_code(ModuleDependencies& dependencies, std: dependencies.audio_spectrum |= contains_two_or_more("_audio_spectrum", shader_code); } -void update_dependencies_from_nodes_graph(ModuleDependencies& dependencies, Cool::NodesGraph const& graph) +void update_dependencies_from_nodes_graph(ModuleDependencies& dependencies, Cool::NodesGraph const& graph, std::vector const& nodes_that_we_depend_on) { dependencies.osc_channels.clear(); dependencies.midi_channels.clear(); - graph.for_each_node([&](Node const& node) { // TODO(Modules) Only check for the nodes that are actually compiled in the graph. Otherwise we might pick up dependencies from other modules or from unused nodes. - for (auto const& value_input : node.value_inputs()) - { + for (auto const& node_id : nodes_that_we_depend_on) + { + graph.nodes().with_ref(node_id, [&](Cool::Node const& abstract_node) { + auto const& node = abstract_node.downcast(); + for (auto const& value_input : node.value_inputs()) { - auto const* osc_channel = std::get_if>(&value_input); - if (osc_channel) - dependencies.osc_channels.insert(osc_channel->value()); + { + auto const* osc_channel = std::get_if>(&value_input); + if (osc_channel) + dependencies.osc_channels.insert(osc_channel->value()); + } + { + auto const* midi_channel = std::get_if>(&value_input); + if (midi_channel) + dependencies.midi_channels.insert(midi_channel->value()); + } + { + auto const* video_file = std::get_if>(&value_input); + if (video_file) + dependencies.time = true; + } } - { - auto const* midi_channel = std::get_if>(&value_input); - if (midi_channel) - dependencies.midi_channels.insert(midi_channel->value()); - } - { - auto const* video_file = std::get_if>(&value_input); - if (video_file) - dependencies.time = true; - } - } - }); + }); + } } } // namespace Lab diff --git a/src/Module/ModuleDependencies.h b/src/Module/ModuleDependencies.h index 0ba03c6ce..ed0d7fae9 100644 --- a/src/Module/ModuleDependencies.h +++ b/src/Module/ModuleDependencies.h @@ -23,7 +23,7 @@ struct ModuleDependencies { auto midi_channel(Cool::MidiChannel const& midi_channel) const -> bool { return midi_channels.find(midi_channel) != midi_channels.end(); } }; -void update_dependencies_from_shader_code(ModuleDependencies& dependencies, std::string shader_code); -void update_dependencies_from_nodes_graph(ModuleDependencies& dependencies, Cool::NodesGraph const&); +void update_dependencies_from_shader_code(ModuleDependencies&, std::string shader_code); +void update_dependencies_from_nodes_graph(ModuleDependencies&, Cool::NodesGraph const&, std::vector const& nodes_that_we_depend_on); } // namespace Lab diff --git a/src/Module/ShaderBased/DataToPassToShader.hpp b/src/Module/ShaderBased/DataToPassToShader.hpp index 5a6693338..6a56cd17f 100644 --- a/src/Module/ShaderBased/DataToPassToShader.hpp +++ b/src/Module/ShaderBased/DataToPassToShader.hpp @@ -1,14 +1,12 @@ #pragma once -#include "Cool/Gpu/DoubleBufferedRenderTarget.h" #include "Cool/Nodes/NodesGraph.h" #include "Dependencies/SystemValues.h" namespace Lab { struct DataToPassToShader { - SystemValues system_values; - Cool::NodesGraph const& nodes_graph; // NOLINT(*avoid-const-or-ref-data-members) - Cool::DoubleBufferedRenderTarget const& feedback_double_buffer; // NOLINT(*avoid-const-or-ref-data-members) + SystemValues system_values; + Cool::NodesGraph const& nodes_graph; // NOLINT(*avoid-const-or-ref-data-members) }; } // namespace Lab \ No newline at end of file diff --git a/src/Module/ShaderBased/generate_shader_code.cpp b/src/Module/ShaderBased/generate_shader_code.cpp index 3863d406a..81ed22f2c 100644 --- a/src/Module/ShaderBased/generate_shader_code.cpp +++ b/src/Module/ShaderBased/generate_shader_code.cpp @@ -97,7 +97,6 @@ uniform sampler1D _audio_spectrum; uniform sampler1D _audio_waveform; uniform mat3 _camera2D_transform; uniform mat3 _camera2D_view; -uniform sampler2D _previous_frame_texture; uniform sampler2D mixbox_lut; // The uniform must have this exact name that mixbox.glsl expects. {modules_textures_uniforms} diff --git a/src/Module/ShaderBased/set_uniforms_for_shader_based_module.cpp b/src/Module/ShaderBased/set_uniforms_for_shader_based_module.cpp index f272e39de..aaf0b61ee 100644 --- a/src/Module/ShaderBased/set_uniforms_for_shader_based_module.cpp +++ b/src/Module/ShaderBased/set_uniforms_for_shader_based_module.cpp @@ -6,6 +6,7 @@ #include "Cool/Midi/MidiManager.h" #include "Cool/StrongTypes/set_uniform.h" #include "Cool/TextureSource/TextureLibrary_Image.h" +#include "Module/Module.h" #include "Nodes/Node.h" #include "Nodes/valid_input_name.h" @@ -88,9 +89,11 @@ static void set_uniform(Cool::OpenGL::Shader const& shader, Cool::SharedVariable } void set_uniforms_for_shader_based_module( - Cool::OpenGL::Shader const& shader, - ModuleDependencies const& depends_on, - DataToPassToShader const& data + Cool::OpenGL::Shader const& shader, + ModuleDependencies const& depends_on, + DataToPassToShader const& data, + std::vector> const& modules_that_we_depend_on, + std::vector const& nodes_that_we_depend_on ) { shader.bind(); @@ -113,25 +116,33 @@ void set_uniforms_for_shader_based_module( if (depends_on.audio_spectrum) shader.set_uniform_texture1D("_audio_spectrum", data.system_values.audio_manager.get().spectrum_texture().id()); - shader.set_uniform_texture( - "_previous_frame_texture", - data.feedback_double_buffer.read_target().get().texture_id(), - Cool::TextureSamplerDescriptor{ - .repeat_mode = Cool::TextureRepeatMode::None, - .interpolation_mode = glpp::Interpolation::NearestNeighbour, // Very important. If set to linear, artifacts can appear over time (very visible with the Slit Scan effect). - } - ); Cool::CameraShaderU::set_uniform(shader, data.system_values.camera_3D, data.system_values.aspect_ratio()); - data.nodes_graph.for_each_node([&](Node const& node) { // TODO(Modules) Only set it for nodes that are actually compiled in the graph. Otherwise causes problems, e.g. if a webcam node is here but unused, we still request webcam capture every frame, which forces us to rerender every frame for no reason + it does extra work. // TODO(Modules) Each module should store a list of its inputs, so that we can set them there - for (auto const& value_input : node.value_inputs()) - { - std::visit([&](auto&& value_input) { - set_uniform(shader, value_input); - }, - value_input); - } - }); + for (auto const& node_id : nodes_that_we_depend_on) + { + data.nodes_graph.nodes().with_ref(node_id, [&](Cool::Node const& abstract_node) { + auto const& node = abstract_node.downcast(); + for (auto const& value_input : node.value_inputs()) + { + std::visit([&](auto&& value_input) { + set_uniform(shader, value_input); + }, + value_input); + } + }); + } + + for (auto const& module : modules_that_we_depend_on) + { + shader.set_uniform_texture( + module->texture_name_in_shader(), + module->texture().id, + Cool::TextureSamplerDescriptor{ + .repeat_mode = Cool::TextureRepeatMode::None, + .interpolation_mode = glpp::Interpolation::NearestNeighbour, // TODO(FeedbackLoop) The texture coming from feedback loop module must use nearest neighbour interpolation (cf // Very important. If set to linear, artifacts can appear over time (very visible with the Slit Scan effect).), but the texture from other modules is probably better off using Linear ??? + } + ); + } } } // namespace Lab \ No newline at end of file diff --git a/src/Module/ShaderBased/set_uniforms_for_shader_based_module.hpp b/src/Module/ShaderBased/set_uniforms_for_shader_based_module.hpp index b779206f4..16528022c 100644 --- a/src/Module/ShaderBased/set_uniforms_for_shader_based_module.hpp +++ b/src/Module/ShaderBased/set_uniforms_for_shader_based_module.hpp @@ -5,6 +5,14 @@ namespace Lab { -void set_uniforms_for_shader_based_module(Cool::OpenGL::Shader const&, ModuleDependencies const&, DataToPassToShader const&); +class Module; + +void set_uniforms_for_shader_based_module( + Cool::OpenGL::Shader const&, + ModuleDependencies const&, + DataToPassToShader const&, + std::vector> const& modules_that_we_depend_on, + std::vector const& nodes_that_we_depend_on +); } // namespace Lab \ No newline at end of file diff --git a/src/Module_Compositing/Module_Compositing.cpp b/src/Module_Compositing/Module_Compositing.cpp index 2e531a27c..ca1660f02 100644 --- a/src/Module_Compositing/Module_Compositing.cpp +++ b/src/Module_Compositing/Module_Compositing.cpp @@ -3,8 +3,19 @@ namespace Lab { -Module_Compositing::Module_Compositing() - : Module{"Compositing"} +static auto module_id() +{ + static auto i{0}; + return i++; +} + +Module_Compositing::Module_Compositing(std::string texture_name_in_shader, std::vector> modules_that_we_depend_on, std::vector nodes_that_we_depend_on) + : Module{ + fmt::format("Compositing {}", module_id()), + std::move(texture_name_in_shader), + std::move(modules_that_we_depend_on), + std::move(nodes_that_we_depend_on) + } { } @@ -20,11 +31,6 @@ void Module_Compositing::reset_shader() _depends_on = {}; } -void Module_Compositing::on_time_reset() -{ - _feedback_double_buffer.clear_render_targets(); -} - void Module_Compositing::set_shader_code(tl::expected const& shader_code) { if (!shader_code) @@ -46,45 +52,28 @@ void Module_Compositing::log_shader_error(Cool::OptionalErrorMessage const& mayb log_module_error(maybe_err, _shader_error_sender); } -void Module_Compositing::imgui_windows(Ui_Ref) const -{ -} - -void Module_Compositing::imgui_show_generated_shader_code() +void Module_Compositing::imgui_generated_shader_code_tab() { - if (Cool::ImGuiExtras::input_text_multiline("##Compositing shader code", &_shader_code, ImVec2{-1.f, -1.f})) + if (ImGui::BeginTabItem(name().c_str())) { - set_shader_code(_shader_code); + if (Cool::ImGuiExtras::input_text_multiline("##Compositing shader code", &_shader_code, ImVec2{-1.f, -1.f})) + set_shader_code(_shader_code); + ImGui::EndTabItem(); } } -void Module_Compositing::set_render_target_size(img::Size const& size) -{ - _feedback_double_buffer.write_target().set_size(size); - _feedback_double_buffer.set_read_target_size_immediately(size); -} - void Module_Compositing::render(DataToPassToShader const& data) -{ - // TODO(Performance) Render only once and then copy to the _feedback_double_buffer ? - // TODO(Performance) Only render on the _feedback_double_buffer when someone depends on it - // Render on the normal render target - render_impl(data); - - // Render on the feedback texture - _feedback_double_buffer.write_target().render([&]() { - render_impl(data); - }); - _feedback_double_buffer.swap_buffers(); -} - -void Module_Compositing::render_impl(DataToPassToShader const& data) { if (!_pipeline.shader()) return; - set_uniforms_for_shader_based_module(*_pipeline.shader(), _depends_on, data); - _pipeline.draw(); + render_target().set_size(data.system_values.render_target_size); + render_target().render([&]() { + glClearColor(0.f, 0.f, 0.f, 0.f); + glClear(GL_COLOR_BUFFER_BIT); + set_uniforms_for_shader_based_module(*_pipeline.shader(), _depends_on, data, modules_that_we_depend_on(), nodes_that_we_depend_on()); + _pipeline.draw(); + }); } } // namespace Lab diff --git a/src/Module_Compositing/Module_Compositing.h b/src/Module_Compositing/Module_Compositing.h index 9f38431a8..75ead3ad9 100644 --- a/src/Module_Compositing/Module_Compositing.h +++ b/src/Module_Compositing/Module_Compositing.h @@ -1,8 +1,6 @@ #pragma once #include #include -#include -#include "Cool/Gpu/DoubleBufferedRenderTarget.h" #include "Cool/Gpu/FullscreenPipeline.h" #include "Module/Module.h" @@ -10,40 +8,28 @@ namespace Lab { class Module_Compositing : public Module { public: - Module_Compositing(); + Module_Compositing() = default; + Module_Compositing(std::string texture_name_in_shader, std::vector> modules_that_we_depend_on, std::vector nodes_that_we_depend_on); Module_Compositing(Module_Compositing const&) = delete; auto operator=(Module_Compositing const&) -> Module_Compositing& = delete; // Module_Compositing(Module_Compositing&&) noexcept = default; // TODO(Modules) // auto operator=(Module_Compositing&&) noexcept -> Module_Compositing& = default; // TODO(Modules) void update() override; - void imgui_windows(Ui_Ref) const override; - void imgui_show_generated_shader_code(); + void imgui_generated_shader_code_tab() override; void reset_shader(); - void on_time_reset(); - void set_render_target_size(img::Size const& size); void set_shader_code(tl::expected const& shader_code); - [[nodiscard]] auto depends_on() const -> ModuleDependencies const& { return _depends_on; } - void update_dependencies_from_nodes_graph(Cool::NodesGraph const& graph) { Lab::update_dependencies_from_nodes_graph(_depends_on, graph); } - - auto shader_is_valid() const -> bool { return _pipeline.shader().has_value(); } // TODO(Modules) Remove - auto shader() -> auto const& { return *_pipeline.shader(); } // TODO(Modules) Remove - auto feedback_double_buffer() const -> Cool::DoubleBufferedRenderTarget const& { return _feedback_double_buffer; } - private: void render(DataToPassToShader const&) override; - void render_impl(DataToPassToShader const&); void log_shader_error(Cool::OptionalErrorMessage const&) const; private: mutable std::string _shader_code{}; mutable Cool::FullscreenPipeline _pipeline{}; mutable Cool::MessageSender _shader_error_sender{}; - Cool::DoubleBufferedRenderTarget _feedback_double_buffer{}; - mutable ModuleDependencies _depends_on{}; private: // Serialization diff --git a/src/Module_Default/Module_Default.cpp b/src/Module_Default/Module_Default.cpp new file mode 100644 index 000000000..ae3fe2527 --- /dev/null +++ b/src/Module_Default/Module_Default.cpp @@ -0,0 +1,29 @@ +#include "Module_Default.hpp" + +namespace Lab { + +static auto module_id() +{ + static auto i{0}; + return i++; +} + +Module_Default::Module_Default(std::string texture_name_in_shader) + : Module{ + fmt::format("Default {}", module_id()), + std::move(texture_name_in_shader), + {}, // We don't depend on any module + {} // We don't depend on any node + } +{} + +void Module_Default::render(DataToPassToShader const& data) +{ + render_target().set_size(data.system_values.render_target_size); + render_target().render([&]() { + glClearColor(0.f, 0.f, 0.f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); + }); +} + +} // namespace Lab diff --git a/src/Module_Default/Module_Default.hpp b/src/Module_Default/Module_Default.hpp new file mode 100644 index 000000000..e84c2d057 --- /dev/null +++ b/src/Module_Default/Module_Default.hpp @@ -0,0 +1,31 @@ +#pragma once +#include "Module/Module.h" +#include "Module/ModuleDependencies.h" + +namespace Lab { +class Module_Default : public Module { +public: + Module_Default() = default; + Module_Default(std::string texture_name_in_shader); + Module_Default(Module_Default const&) = delete; + auto operator=(Module_Default const&) -> Module_Default& = delete; + Module_Default(Module_Default&&) noexcept = default; + auto operator=(Module_Default&&) noexcept -> Module_Default& = default; + ~Module_Default() override = default; + +private: + void render(DataToPassToShader const&) override; + +private: + // Serialization + friend class ser20::access; + template + void serialize(Archive& archive) + { + archive( + ser20::make_nvp("Base Module", ser20::base_class(this)) + ); + } +}; + +} // namespace Lab diff --git a/src/Module_FeedbackLoop/Module_FeedbackLoop.cpp b/src/Module_FeedbackLoop/Module_FeedbackLoop.cpp new file mode 100644 index 000000000..2af23d34e --- /dev/null +++ b/src/Module_FeedbackLoop/Module_FeedbackLoop.cpp @@ -0,0 +1,59 @@ +#include "Module_FeedbackLoop.hpp" +#include "Cool/Gpu/OpenGL/copy_tex_pipeline.hpp" + +namespace Lab { + +static auto module_id() +{ + static auto i{0}; + return i++; +} + +Module_FeedbackLoop::Module_FeedbackLoop(std::string texture_name_in_shader, std::shared_ptr module_that_we_depend_on) + : Module{ + fmt::format("Feedback Loop {}", module_id()), + std::move(texture_name_in_shader), + {std::move(module_that_we_depend_on)}, + {} // We don't depend on any node + } +{} + +void Module_FeedbackLoop::on_time_reset() +{ + _renders_count = 0; +} + +auto Module_FeedbackLoop::texture() const -> Cool::TextureRef +{ + auto const b = _renders_count < 2 ? !_render_target_ping_pong : _render_target_ping_pong; // During the first frame, the previous frame texture is not init, so use the current frame texture instead + return (b ? _render_target : render_target()).texture_ref(); +} + +void Module_FeedbackLoop::render(DataToPassToShader const& data) +{ + _render_target_ping_pong = !_render_target_ping_pong; + _renders_count++; + auto& rt = _render_target_ping_pong ? render_target() : _render_target; + rt.set_size(data.system_values.render_target_size); + rt.render([&]() { + // TODO(WebGPU) use a texture copy operation instead, it will be more efficient + Cool::copy_tex_pipeline().shader()->bind(); + Cool::copy_tex_pipeline().shader()->set_uniform_texture("tex_to_copy", modules_that_we_depend_on()[0]->texture().id); + glDisable(GL_BLEND); + Cool::copy_tex_pipeline().draw(); + glEnable(GL_BLEND); + }); +} + +void Module_FeedbackLoop::before_module_graph_renders() +{ + _rerender_this_frame = _rerender_next_frame; + _rerender_next_frame = Module::needs_to_rerender(); // Make sure we will also render one frame after our dependencies rendered +} + +auto Module_FeedbackLoop::needs_to_rerender() const -> bool +{ + return Module::needs_to_rerender() || _rerender_this_frame; +} + +} // namespace Lab diff --git a/src/Module_FeedbackLoop/Module_FeedbackLoop.hpp b/src/Module_FeedbackLoop/Module_FeedbackLoop.hpp new file mode 100644 index 000000000..1ca32136d --- /dev/null +++ b/src/Module_FeedbackLoop/Module_FeedbackLoop.hpp @@ -0,0 +1,43 @@ +#pragma once +#include "Module/Module.h" +#include "Module/ModuleDependencies.h" + +namespace Lab { +class Module_FeedbackLoop : public Module { +public: + Module_FeedbackLoop() = default; + Module_FeedbackLoop(std::string texture_name_in_shader, std::shared_ptr module_that_we_depend_on); + Module_FeedbackLoop(Module_FeedbackLoop const&) = delete; + auto operator=(Module_FeedbackLoop const&) -> Module_FeedbackLoop& = delete; + Module_FeedbackLoop(Module_FeedbackLoop&&) noexcept = default; + auto operator=(Module_FeedbackLoop&&) noexcept -> Module_FeedbackLoop& = default; + ~Module_FeedbackLoop() override = default; + + void on_time_reset() override; + auto texture() const -> Cool::TextureRef override; + [[nodiscard]] auto needs_to_rerender() const -> bool override; + void before_module_graph_renders() override; + +private: + void render(DataToPassToShader const&) override; + +private: + Cool::RenderTarget _render_target{}; + bool _render_target_ping_pong{false}; + int _renders_count{0}; + bool _rerender_next_frame{false}; + bool _rerender_this_frame{false}; + +private: + // Serialization + friend class ser20::access; + template + void serialize(Archive& archive) + { + archive( + ser20::make_nvp("Base Module", ser20::base_class(this)) + ); + } +}; + +} // namespace Lab diff --git a/src/Module_Particles/Module_Particles.cpp b/src/Module_Particles/Module_Particles.cpp index 03dafd109..0e55aef63 100644 --- a/src/Module_Particles/Module_Particles.cpp +++ b/src/Module_Particles/Module_Particles.cpp @@ -5,8 +5,19 @@ namespace Lab { -Module_Particles::Module_Particles(Cool::NodeId const& id_of_node_storing_particles_count) - : Module{"Particles"} +static auto module_id() +{ + static auto i{0}; + return i++; +} + +Module_Particles::Module_Particles(Cool::NodeId const& id_of_node_storing_particles_count, std::string texture_name_in_shader, std::vector> modules_that_we_depend_on, std::vector nodes_that_we_depend_on) + : Module{ + fmt::format("Particles {}", module_id()), + std::move(texture_name_in_shader), + std::move(modules_that_we_depend_on), + std::move(nodes_that_we_depend_on) + } , _id_of_node_storing_particles_count{id_of_node_storing_particles_count} { } @@ -65,7 +76,8 @@ void Module_Particles::set_simulation_shader_code(tl::expectedsimulation_shader().bind(); _particle_system->simulation_shader().set_uniform("_force_init_particles", _force_init_particles); - set_uniforms_for_shader_based_module(_particle_system->simulation_shader(), _depends_on, data); + set_uniforms_for_shader_based_module(_particle_system->simulation_shader(), _depends_on, data, modules_that_we_depend_on(), nodes_that_we_depend_on()); _particle_system->update(); _force_init_particles = false; _needs_to_update_particles = false; @@ -131,14 +148,14 @@ void Module_Particles::update_particles(DataToPassToShader const& data) #endif } -void Module_Particles::imgui_windows(Ui_Ref) const +void Module_Particles::imgui_generated_shader_code_tab() { -} - -void Module_Particles::imgui_show_generated_shader_code() -{ - if (Cool::ImGuiExtras::input_text_multiline("##Particles simulation", &_shader_code, ImVec2{-1.f, -1.f})) - set_simulation_shader_code(_shader_code, false, _particle_system ? _particle_system->dimension() : _particle_system_dimension); + if (ImGui::BeginTabItem(fmt::format("{} (Simulation)", name()).c_str())) + { + if (Cool::ImGuiExtras::input_text_multiline("##Particles simulation", &_shader_code, ImVec2{-1.f, -1.f})) + set_simulation_shader_code(_shader_code, false, _particle_system ? _particle_system->dimension() : _particle_system_dimension); + ImGui::EndTabItem(); + } } void Module_Particles::render(DataToPassToShader const& data) @@ -150,26 +167,31 @@ void Module_Particles::render(DataToPassToShader const& data) update_particles(data); #if !defined(COOL_PARTICLES_DISABLED_REASON) - set_uniforms_for_shader_based_module(_particle_system->render_shader(), _depends_on, data); - - auto const view_proj_matrix_2D_mat3 = data.system_values.camera_2D_view_projection_matrix(); - auto const view_proj_matrix_2D_mat4 = glm::mat4{ - glm::vec4{view_proj_matrix_2D_mat3[0], 0.f}, - glm::vec4{view_proj_matrix_2D_mat3[1], 0.f}, - glm::vec4{0.f}, - glm::vec4{view_proj_matrix_2D_mat3[2][0], view_proj_matrix_2D_mat3[2][1], 0.f, view_proj_matrix_2D_mat3[2][2]} - }; - - if (_particle_system->dimension() == 2) - { - _particle_system->render_shader().set_uniform("view_proj_matrix", view_proj_matrix_2D_mat4); - } - else if (_particle_system->dimension() == 3) - { - _particle_system->render_shader().set_uniform("view_proj_matrix", view_proj_matrix_2D_mat4 * data.system_values.camera_3D.view_projection_matrix(1.f /* The aspect ratio is already taken into account in the camera 2D matrix */)); - _particle_system->render_shader().set_uniform("cool_camera_view", data.system_values.camera_3D.view_matrix()); - } - _particle_system->render(); + render_target().set_size(data.system_values.render_target_size); + render_target().render([&]() { + glClearColor(0.f, 0.f, 0.f, 0.f); + glClear(GL_COLOR_BUFFER_BIT); + set_uniforms_for_shader_based_module(_particle_system->render_shader(), _depends_on, data, modules_that_we_depend_on(), nodes_that_we_depend_on()); + + auto const view_proj_matrix_2D_mat3 = data.system_values.camera_2D_view_projection_matrix(); + auto const view_proj_matrix_2D_mat4 = glm::mat4{ + glm::vec4{view_proj_matrix_2D_mat3[0], 0.f}, + glm::vec4{view_proj_matrix_2D_mat3[1], 0.f}, + glm::vec4{0.f}, + glm::vec4{view_proj_matrix_2D_mat3[2][0], view_proj_matrix_2D_mat3[2][1], 0.f, view_proj_matrix_2D_mat3[2][2]} + }; + + if (_particle_system->dimension() == 2) + { + _particle_system->render_shader().set_uniform("view_proj_matrix", view_proj_matrix_2D_mat4); + } + else if (_particle_system->dimension() == 3) + { + _particle_system->render_shader().set_uniform("view_proj_matrix", view_proj_matrix_2D_mat4 * data.system_values.camera_3D.view_projection_matrix(1.f /* The aspect ratio is already taken into account in the camera 2D matrix */)); + _particle_system->render_shader().set_uniform("cool_camera_view", data.system_values.camera_3D.view_matrix()); + } + _particle_system->render(); + }); #endif } diff --git a/src/Module_Particles/Module_Particles.h b/src/Module_Particles/Module_Particles.h index 261fb00a1..290a400f6 100644 --- a/src/Module_Particles/Module_Particles.h +++ b/src/Module_Particles/Module_Particles.h @@ -1,5 +1,4 @@ #pragma once -#include "Cool/Gpu/DoubleBufferedRenderTarget.h" #include "Cool/Log/OptionalErrorMessage.h" #include "Cool/Nodes/NodeId.h" #include "Cool/Nodes/NodesGraph.h" @@ -11,20 +10,16 @@ namespace Lab { class Module_Particles : public Module { public: Module_Particles() = default; - explicit Module_Particles(Cool::NodeId const& id_of_node_storing_particles_count); + explicit Module_Particles(Cool::NodeId const& id_of_node_storing_particles_count, std::string texture_name_in_shader, std::vector> modules_that_we_depend_on, std::vector nodes_that_we_depend_on); Module_Particles(Module_Particles const&) = delete; auto operator=(Module_Particles const&) -> Module_Particles& = delete; Module_Particles(Module_Particles&&) noexcept = default; auto operator=(Module_Particles&&) noexcept -> Module_Particles& = default; ~Module_Particles() override = default; - Cool::NodesGraph const* _nodes_graph{}; // TODO(Particles) Remove - Cool::DoubleBufferedRenderTarget const* _feedback_double_buffer{}; // TODO(Particles) Remove - void update() override; - void request_particles_to_update() { _needs_to_update_particles = true; } - void imgui_windows(Ui_Ref) const override; - void imgui_show_generated_shader_code(); + void on_time_changed() override; + void imgui_generated_shader_code_tab() override; [[nodiscard]] auto needs_to_rerender() const -> bool override { @@ -32,10 +27,7 @@ class Module_Particles : public Module { }; void set_simulation_shader_code(tl::expected const& shader_code, bool for_testing_nodes, int dimension); - void on_time_reset(); - - [[nodiscard]] auto depends_on() const -> ModuleDependencies const& { return _depends_on; } - void update_dependencies_from_nodes_graph(Cool::NodesGraph const& graph) { Lab::update_dependencies_from_nodes_graph(_depends_on, graph); } + void on_time_reset() override; private: void render(DataToPassToShader const&) override; @@ -45,16 +37,17 @@ class Module_Particles : public Module { auto desired_particles_count() const -> size_t; void log_simulation_shader_error(Cool::OptionalErrorMessage const&) const; void request_particles_to_reset(); + void request_particles_to_update() { _needs_to_update_particles = true; } private: mutable std::optional _particle_system{}; int _particle_system_dimension{}; - ModuleDependencies _depends_on{}; // TODO(Particles) Two dependencies, one for each shader (simulation and render) - Cool::NodeId _id_of_node_storing_particles_count{}; - bool _needs_to_update_particles{true}; - bool _force_init_particles{true}; - mutable Cool::MessageSender _simulation_shader_error_sender{}; - mutable std::string _shader_code{}; + // ModuleDependencies _depends_on{}; // TODO(Particles) Two dependencies, one for each shader (simulation and render) + Cool::NodeId _id_of_node_storing_particles_count{}; + bool _needs_to_update_particles{true}; + bool _force_init_particles{true}; + mutable Cool::MessageSender _simulation_shader_error_sender{}; + mutable std::string _shader_code{}; private: // Serialization diff --git a/src/Module_Particles/generate_simulation_shader_code.cpp b/src/Module_Particles/generate_simulation_shader_code.cpp index 04660890d..fd2f80fdf 100644 --- a/src/Module_Particles/generate_simulation_shader_code.cpp +++ b/src/Module_Particles/generate_simulation_shader_code.cpp @@ -10,7 +10,8 @@ auto generate_simulation_shader_code( Cool::NodeId const& root_node_id, Cool::NodeId& id_of_node_storing_particles_count, int dimension, - DataToGenerateShaderCode const& data + DataToGenerateShaderCode const& data, + MaybeGenerateModule const& maybe_generate_module ) -> tl::expected { using fmt::literals::operator""_a; @@ -142,11 +143,11 @@ void cool_main() .arity = 1, }; - auto const node_definition_callback = [&](auto const& node_id, auto const&) { + auto const node_definition_callback = [&](Cool::NodeId const& node_id, NodeDefinition const& node_definition) { auto const* maybe_node = data.nodes_graph.try_get_node(node_id); if (maybe_node && maybe_node->particles_count().has_value()) id_of_node_storing_particles_count = node_id; - return std::nullopt; + return maybe_generate_module(node_id, node_definition); }; return generate_shader_code( diff --git a/src/Module_Particles/generate_simulation_shader_code.h b/src/Module_Particles/generate_simulation_shader_code.h index 05c86b763..6c821809f 100644 --- a/src/Module_Particles/generate_simulation_shader_code.h +++ b/src/Module_Particles/generate_simulation_shader_code.h @@ -1,5 +1,6 @@ #pragma once #include "Module/ShaderBased/DataToGenerateShaderCode.hpp" +#include "Nodes/MaybeGenerateModule.h" namespace Lab { @@ -7,7 +8,8 @@ auto generate_simulation_shader_code( Cool::NodeId const& root_node_id, Cool::NodeId& id_of_node_storing_particles_count, int dimension, - DataToGenerateShaderCode const& + DataToGenerateShaderCode const&, + MaybeGenerateModule const& ) -> tl::expected; } // namespace Lab diff --git a/src/ModulesGraph/DirtyFlags.cpp b/src/ModulesGraph/DirtyFlags.cpp index eeee530e9..e9ba64d52 100644 --- a/src/ModulesGraph/DirtyFlags.cpp +++ b/src/ModulesGraph/DirtyFlags.cpp @@ -4,12 +4,12 @@ namespace Lab { auto DirtyFlags::primary(bool always_requires_shader_code_generation) const -> Cool::DirtyFlag const& { - return always_requires_shader_code_generation ? regenerate_code : rerender; + return always_requires_shader_code_generation ? rebuild : rerender; } auto DirtyFlags::secondary() const -> Cool::DirtyFlag const& { - return regenerate_code; // At the moment only used by Gradient variable when we detect that the number of marks has changed. See `set_value()` of Command_SetVariable.h + return rebuild; // At the moment only used by Gradient variable when we detect that the number of marks has changed. See `set_value()` of Command_SetVariable.h } } // namespace Lab \ No newline at end of file diff --git a/src/ModulesGraph/DirtyFlags.h b/src/ModulesGraph/DirtyFlags.h index f90fe19b8..d5c17db7d 100644 --- a/src/ModulesGraph/DirtyFlags.h +++ b/src/ModulesGraph/DirtyFlags.h @@ -5,7 +5,7 @@ namespace Lab { struct DirtyFlags { Cool::DirtyFlag rerender{}; - Cool::DirtyFlag regenerate_code{}; // TODO(Modules) Rename as graph_has_changed_flag + Cool::DirtyFlag rebuild{}; /// These two functions are used by variables to know which flag they should use auto primary(bool always_requires_shader_code_generation) const -> Cool::DirtyFlag const&; @@ -19,7 +19,7 @@ struct DirtyFlags { { archive( ser20::make_nvp("Rerender", rerender), - ser20::make_nvp("Regenerate code", regenerate_code) + ser20::make_nvp("Rebuild", rebuild) ); } }; diff --git a/src/ModulesGraph/ModulesGraph.cpp b/src/ModulesGraph/ModulesGraph.cpp index 94b7cffd8..1e86e75eb 100644 --- a/src/ModulesGraph/ModulesGraph.cpp +++ b/src/ModulesGraph/ModulesGraph.cpp @@ -5,11 +5,13 @@ #include #include #include "Cool/Audio/AudioManager.h" -#include "Cool/Camera/CameraShaderU.h" -#include "Cool/Gpu/RenderTarget.h" #include "Cool/Nodes/NodesLibrary.h" #include "Cool/StrongTypes/Camera2D.h" +#include "Module_Compositing/Module_Compositing.h" #include "Module_Compositing/generate_compositing_shader_code.h" +#include "Module_Default/Module_Default.hpp" +#include "Module_FeedbackLoop/Module_FeedbackLoop.hpp" +#include "Module_Particles/Module_Particles.h" #include "Module_Particles/generate_simulation_shader_code.h" #include "Nodes/valid_glsl.h" #include "UI/imgui_show.h" @@ -18,210 +20,294 @@ namespace Lab { void ModulesGraph::update() { - _compositing_module.update(); - for (auto& module_node : _particles_module_nodes) + for (auto const& module : _modules) { - module_node->module._nodes_graph = &_nodes_editor.graph(); - module_node->module.update(); + module->_nodes_graph = &_nodes_editor.graph(); + module->update(); } } -void ModulesGraph::render(Cool::RenderTarget& render_target, DataToPassToShader const& data_to_pass_to_shader, DataToGenerateShaderCode const& data_to_generate_shader_code) +void ModulesGraph::check_for_rerender_and_rebuild(DataToPassToShader const& data_to_pass_to_shader, DataToGenerateShaderCode const& data_to_generate_shader_code) { - if (_compositing_module.depends_on().time_since_last_midi_button_pressed - || std::any_of(_particles_module_nodes.begin(), _particles_module_nodes.end(), [&](auto const& module_node) { return module_node->module.depends_on().time_since_last_midi_button_pressed; })) + if (rebuild_modules_graph_flag().is_dirty()) { - request_rerender_all(); // TODO(Modules) Only rerender the modules that depend on this + if (DebugOptions::log_when_compiling_nodes()) + Cool::Log::ToUser::info("Modules Graph", "Rebuilt"); + recreate_all_modules(_main_node_id, data_to_generate_shader_code); + rebuild_modules_graph_flag().set_clean(); } - if (render_target.needs_resizing()) - request_rerender_all(); + if (rerender_all_flag().is_dirty()) { request_rerender_all(); rerender_all_flag().set_clean(); } - if (regenerate_code_flag().is_dirty()) + for (auto const& module : _modules) { - if (DebugOptions::log_when_compiling_nodes()) - Cool::Log::ToUser::info("Modules Graph", "Compiled"); - create_and_compile_all_modules(_main_node_id, data_to_generate_shader_code); - request_rerender_all(); - regenerate_code_flag().set_clean(); + // Modules that depend on time_since_last_midi_button_pressed should rerender every frame + if (module->depends_on().time_since_last_midi_button_pressed) + module->needs_to_rerender_flag().set_dirty(); + + // Rerender when render size changes + if (data_to_pass_to_shader.system_values.render_target_size != module->texture().size) + module->needs_to_rerender_flag().set_dirty(); } +} - for (auto& module_node : _particles_module_nodes) - module_node->render_target.set_size(render_target.desired_size()); - _compositing_module.set_render_target_size(render_target.desired_size()); // Must be done before rendering, otherwise we might read a target that is too small. (e.g. 1 pixel on app startup) +void ModulesGraph::render(DataToPassToShader const& data_to_pass_to_shader, DataToGenerateShaderCode const& data_to_generate_shader_code) +{ + check_for_rerender_and_rebuild(data_to_pass_to_shader, data_to_generate_shader_code); - // TODO(Particles) Remove those _nodes_graph - for (auto& module_node : _particles_module_nodes) + for (auto& module : _modules) { - module_node->module._nodes_graph = &_nodes_editor.graph(); - module_node->module._feedback_double_buffer = &_compositing_module.feedback_double_buffer(); - if (module_node->module.needs_to_rerender()) - _compositing_module.needs_to_rerender_flag().set_dirty(); // Because compositing module depends on particles module + module->_nodes_graph = &_nodes_editor.graph(); // TODO(Particles) Remove those _nodes_graph + module->before_module_graph_renders(); } - // TODO(Modules) Render in the order of dependency between the modules - for (auto& node : _particles_module_nodes) - render_particle_module(node->module, node->render_target, data_to_pass_to_shader); - render_compositing_module(render_target, data_to_pass_to_shader); + + render_module_ifn(*_root_module, data_to_pass_to_shader); } -void ModulesGraph::render_one_module(Module& some_module, Cool::RenderTarget& render_target, DataToPassToShader const& data) +void ModulesGraph::render_module_ifn(Module& module, DataToPassToShader const& data) { - if (!some_module.needs_to_rerender()) + if (!module.needs_to_rerender()) return; - some_module.needs_to_rerender_flag().set_clean(); - render_target.render([&]() { - glClearColor(0.f, 0.f, 0.f, 0.f); - glClear(GL_COLOR_BUFFER_BIT); - some_module.do_rendering(data); - }); + // Render all the dependencies first, so that we can use their textures + for (auto const& prev : module.modules_that_we_depend_on()) + render_module_ifn(*prev, data); + + module.do_rendering(data); + module.needs_to_rerender_flag().set_clean(); if (DebugOptions::log_when_rendering()) - Cool::Log::ToUser::info(some_module.name() + " Module", "Rendered"); + Cool::Log::ToUser::info(module.name(), fmt::format("Rendered ({}x{})", data.system_values.render_target_size.width(), data.system_values.render_target_size.height())); } -void ModulesGraph::render_particle_module(Module_Particles& module, Cool::RenderTarget& render_target, DataToPassToShader const& data) +void ModulesGraph::request_rerender_all() { - render_one_module(module, render_target, data); + for (auto& module : _modules) + module->needs_to_rerender_flag().set_dirty(); } -void ModulesGraph::render_compositing_module(Cool::RenderTarget& render_target, DataToPassToShader const& data) +void ModulesGraph::on_time_reset() { - if (_compositing_module.shader_is_valid()) - { - _compositing_module.shader().bind(); - for (auto const& module_node : _particles_module_nodes) - { - _compositing_module.shader().set_uniform_texture( - module_node->texture_name_in_shader, - module_node->render_target.get().texture_id(), - Cool::TextureSamplerDescriptor{ - .repeat_mode = Cool::TextureRepeatMode::None, - .interpolation_mode = glpp::Interpolation::Linear, - } - ); - } - } + for (auto& module : _modules) + module->on_time_reset(); +} - render_one_module(_compositing_module, render_target, data); +static auto texture_name_for_module(Cool::NodeId const& id) -> std::string +{ + return valid_glsl(fmt::format("texture_{})", to_string(id.underlying_uuid()))); } -void ModulesGraph::request_rerender_all() +enum class NodeModuleness { + Generic, + Particle, + FeedbackLoop, +}; + +static auto is_feedback_loop(NodeDefinition const& node_definition) { - _compositing_module.needs_to_rerender_flag().set_dirty(); - for (auto& node : _particles_module_nodes) - node->module.needs_to_rerender_flag().set_dirty(); + return node_definition.name() == "Feedback (One frame delay)"; } -void ModulesGraph::on_time_reset() +static auto node_moduleness(NodeDefinition const& node_definition) { - _compositing_module.on_time_reset(); - for (auto& node : _particles_module_nodes) - node->module.on_time_reset(); + if (is_particle(node_definition.signature())) + return NodeModuleness::Particle; + if (is_feedback_loop(node_definition)) + return NodeModuleness::FeedbackLoop; + return NodeModuleness::Generic; } -static auto texture_name_for_module(NodeDefinition const& definition, Cool::NodeId const& id) -> std::string +void ModulesGraph::recreate_all_modules(Cool::NodeId const& root_node_id, DataToGenerateShaderCode const& data_to_generate_shader_code) { - using fmt::literals::operator""_a; - return valid_glsl(fmt::format( - FMT_COMPILE( - R"STR(texture_{name}{id})STR" - ), - "name"_a = definition.name(), - "id"_a = to_string(id.underlying_uuid()) - ) - ); + _modules.clear(); + _root_module = create_module(root_node_id, data_to_generate_shader_code); + request_rerender_all(); } -void ModulesGraph::create_and_compile_all_modules(Cool::NodeId const& root_node_id, DataToGenerateShaderCode const& data_to_generate_shader_code) +auto ModulesGraph::create_module(Cool::NodeId const& root_node_id, DataToGenerateShaderCode const& data) -> std::shared_ptr { - _particles_module_nodes.clear(); - _compositing_module.reset_shader(); + auto const* node = data.nodes_graph.try_get_node(root_node_id); + if (!node) + return create_default_module(); // TODO(Module) Return an error message? Probably not because this is legit, eg when a feedback loop has nothing in its input pin + auto const* node_def = data.get_node_definition(node->id_names()); + if (!node_def) + return create_default_module(); // TODO(Module) Return an error message, this shouldn't happen + + switch (node_moduleness(*node_def)) + { + case NodeModuleness::Generic: + return create_compositing_module(root_node_id, data); + case NodeModuleness::Particle: + return create_particles_module(root_node_id, *node_def, data); + case NodeModuleness::FeedbackLoop: + return create_feedback_loop_module(root_node_id, data); + } + assert(false); + return create_default_module(); +} - if (!data_to_generate_shader_code.nodes_graph.try_get_node(root_node_id)) - return; +auto ModulesGraph::create_module_impl(std::string const& texture_name_in_shader, std::function()> const& make_module) -> std::shared_ptr +{ + { // If the module already exists, just return it + auto const it = std::find_if(_modules.begin(), _modules.end(), [&](auto&& module) { + return module->texture_name_in_shader() == texture_name_in_shader; + }); + if (it != _modules.end()) + return *it; + } - auto const shader_code = generate_compositing_shader_code( - root_node_id, - [&](Cool::NodeId const& particles_root_node_id, NodeDefinition const& node_definition) -> std::optional { - if (!is_particle(node_definition.signature())) - return std::nullopt; + // Otherwise create it and add it to the list + _modules.push_back(make_module()); + return _modules.back(); +} - int const dimension = is_particle_3D(node_definition.signature()) ? 3 : 2; - auto const texture_name_in_shader = texture_name_for_module(node_definition, particles_root_node_id); +auto ModulesGraph::create_compositing_module(Cool::NodeId const& root_node_id, DataToGenerateShaderCode const& data) -> std::shared_ptr +{ + auto const texture_name_in_shader = texture_name_for_module(root_node_id); + return create_module_impl(texture_name_in_shader, [&]() -> std::shared_ptr { + auto modules_that_we_depend_on = std::vector>{}; + auto nodes_that_we_depend_on = std::vector{}; + + auto const shader_code = generate_compositing_shader_code( + root_node_id, + [&](Cool::NodeId const& node_id, NodeDefinition const& node_definition) -> std::optional { + switch (node_moduleness(node_definition)) + { + case NodeModuleness::Generic: + { + nodes_that_we_depend_on.push_back(node_id); + return std::nullopt; + } + case NodeModuleness::Particle: + { + modules_that_we_depend_on.push_back(create_particles_module(node_id, node_definition, data)); + return modules_that_we_depend_on.back()->texture_name_in_shader(); + } + case NodeModuleness::FeedbackLoop: + { + modules_that_we_depend_on.push_back(create_feedback_loop_module(node_id, data)); + return modules_that_we_depend_on.back()->texture_name_in_shader(); + } + } + assert(false); + return std::nullopt; + }, + [&]() { + std::vector tex_names; + tex_names.reserve(_modules.size()); + for (auto const& module : _modules) + { + tex_names.push_back(module->texture_name_in_shader()); + } + return tex_names; + }, + data + ); + + auto module = std::make_shared( + texture_name_in_shader, // Don't move it because it might still be used by create_module_impl() + std::move(modules_that_we_depend_on), + std::move(nodes_that_we_depend_on) + ); + module->set_shader_code(shader_code); + return module; + }); +} - if (std::none_of( - _particles_module_nodes.begin(), - _particles_module_nodes.end(), - [&](std::unique_ptr const& node) { - return node->texture_name_in_shader == texture_name_in_shader; - } - )) - { - auto id_of_node_storing_particles_count = Cool::NodeId{}; // Will be initialized by generate_simulation_shader_code() - auto const simulation_shader_code = generate_simulation_shader_code( - particles_root_node_id, - id_of_node_storing_particles_count, - dimension, - data_to_generate_shader_code - ); - - _particles_module_nodes.push_back(std::make_unique( - /* .module = */ Module_Particles(id_of_node_storing_particles_count), - /* .texture_name_in_shader = */ texture_name_in_shader - )); - _particles_module_nodes.back()->module.set_simulation_shader_code(simulation_shader_code, false, dimension); +auto ModulesGraph::create_particles_module(Cool::NodeId const& root_node_id, NodeDefinition const& node_definition, DataToGenerateShaderCode const& data) -> std::shared_ptr +{ + auto const texture_name_in_shader = texture_name_for_module(root_node_id); + return create_module_impl(texture_name_in_shader, [&]() -> std::shared_ptr { + auto modules_that_we_depend_on = std::vector>{}; + auto nodes_that_we_depend_on = std::vector{}; + + int const dimension = is_particle_3D(node_definition.signature()) ? 3 : 2; + + auto id_of_node_storing_particles_count = Cool::NodeId{}; // Will be initialized by generate_simulation_shader_code() + auto const simulation_shader_code = generate_simulation_shader_code( + root_node_id, + id_of_node_storing_particles_count, + dimension, + data, + [&](Cool::NodeId const& node_id, NodeDefinition const& node_definition) -> std::optional { + switch (node_moduleness(node_definition)) + { + case NodeModuleness::Generic: + case NodeModuleness::Particle: // TODO(Particles) This is not quite right, a particle system might depend on the image generated by another particles module + { + nodes_that_we_depend_on.push_back(node_id); + return std::nullopt; + } + case NodeModuleness::FeedbackLoop: + { + modules_that_we_depend_on.push_back(create_feedback_loop_module(node_id, data)); + return modules_that_we_depend_on.back()->texture_name_in_shader(); + } + } + assert(false); + return std::nullopt; } + ); + + auto module = std::make_shared( + id_of_node_storing_particles_count, + texture_name_in_shader, // Don't move it because it might still be used by create_module_impl() + std::move(modules_that_we_depend_on), + std::move(nodes_that_we_depend_on) + ); + module->set_simulation_shader_code(simulation_shader_code, false, dimension); + return module; + }); +} - return texture_name_in_shader; - }, - [&]() { - std::vector tex_names; - tex_names.reserve(_particles_module_nodes.size()); - for (auto const& node : _particles_module_nodes) - { - tex_names.push_back(node->texture_name_in_shader); - } - return tex_names; - }, - data_to_generate_shader_code - ); - _compositing_module.set_shader_code(shader_code); +auto ModulesGraph::create_feedback_loop_module(Cool::NodeId const& root_node_id, DataToGenerateShaderCode const& data) -> std::shared_ptr +{ + auto const texture_name_in_shader = texture_name_for_module(root_node_id); + return create_module_impl(texture_name_in_shader, [&]() -> std::shared_ptr { + auto const* node = data.nodes_graph.try_get_node(root_node_id); + if (!node) + return create_default_module(); // TODO(Module) Return an error message? This should never happen + + assert(node->input_pins().size() == 1); + auto const predecessor_node_id = data.nodes_graph.find_node_connected_to_input_pin(node->input_pins()[0].id()); + auto dependency = create_module(predecessor_node_id, data); + + return std::make_shared( + texture_name_in_shader, // Don't move it because it might still be used by create_module_impl() + std::move(dependency) + ); + }); +} + +auto ModulesGraph::create_default_module() -> std::shared_ptr +{ + auto const texture_name_in_shader = "texture_of_the_default_module"s; + return create_module_impl(texture_name_in_shader, [&]() -> std::shared_ptr { + return std::make_shared(texture_name_in_shader); + }); } void ModulesGraph::imgui_windows(Ui_Ref ui, Cool::AudioManager& audio_manager, Cool::NodesLibrary const& nodes_library) const { - for (auto const& _particles_module : _particles_module_nodes) - { - _particles_module->module.imgui_windows(ui); - } - _compositing_module.imgui_windows(ui); + for (auto const& module : _modules) + module->imgui_windows(ui); { auto cfg = Cool::NodesConfig{nodes_config(ui, audio_manager, nodes_library)}; if (_nodes_editor.imgui_windows(cfg, nodes_library)) - regenerate_code_flag().set_dirty(); + rebuild_modules_graph_flag().set_dirty(); } DebugOptions::show_generated_shader_code([&] { if (ImGui::BeginTabBar("Shaders Tabs", ImGuiTabBarFlags_None)) { - if (ImGui::BeginTabItem("Compositing")) + for (auto const& module : _modules) { - _compositing_module.imgui_show_generated_shader_code(); - ImGui::EndTabItem(); - } - for (auto const& _particles_module : _particles_module_nodes) - { - ImGui::PushID(&_particles_module); - if (ImGui::BeginTabItem("Particle Simulation")) - { - _particles_module->module.imgui_show_generated_shader_code(); - ImGui::EndTabItem(); - } + ImGui::PushID(module.get()); + module->imgui_generated_shader_code_tab(); ImGui::PopID(); } ImGui::EndTabBar(); @@ -258,6 +344,12 @@ void ModulesGraph::submit_gizmos(Cool::GizmoManager& gizmos, CommandExecutor con }); } +auto ModulesGraph::final_texture() const -> Cool::TextureRef +{ + assert(_root_module && "You must call render() before trying to access the final texture"); + return _root_module->texture(); +} + auto ModulesGraph::nodes_config(Ui_Ref ui, Cool::AudioManager& audio_manager, Cool::NodesLibrary const& nodes_library) const -> NodesConfig { return NodesConfig{ @@ -274,58 +366,54 @@ auto ModulesGraph::nodes_config(Ui_Ref ui, Cool::AudioManager& audio_manager, Co void ModulesGraph::on_time_changed() { - for (auto& node : _particles_module_nodes) - { - node->module.request_particles_to_update(); - } - if (_compositing_module.depends_on().time - || !_particles_module_nodes.empty()) + for (auto& module : _modules) { - request_rerender_all(); // TODO(Modules) Only rerender the modules that depend on time + module->on_time_changed(); + if (module->depends_on().time) + module->needs_to_rerender_flag().set_dirty(); } } void ModulesGraph::on_audio_changed() { - if (_compositing_module.depends_on().audio() - || std::any_of(_particles_module_nodes.begin(), _particles_module_nodes.end(), [](auto const& module_node) { return module_node->module.depends_on().audio(); })) + for (auto& module : _modules) { - request_rerender_all(); // TODO(Modules) Only rerender the modules that depend on audio + if (module->depends_on().audio()) + module->needs_to_rerender_flag().set_dirty(); } } void ModulesGraph::on_osc_channel_changed(Cool::OSCChannel const& osc_channel) { - if (_compositing_module.depends_on().osc_channel(osc_channel) - || std::any_of(_particles_module_nodes.begin(), _particles_module_nodes.end(), [&](auto const& module_node) { return module_node->module.depends_on().osc_channel(osc_channel); })) + for (auto& module : _modules) { - request_rerender_all(); // TODO(Modules) Only rerender the modules that depend on this OSC channel + if (module->depends_on().osc_channel(osc_channel)) + module->needs_to_rerender_flag().set_dirty(); } } void ModulesGraph::on_midi_channel_changed(Cool::MidiChannel const& midi_channel) { - if (_compositing_module.depends_on().midi_channel(midi_channel) - || std::any_of(_particles_module_nodes.begin(), _particles_module_nodes.end(), [&](auto const& module_node) { return module_node->module.depends_on().midi_channel(midi_channel); })) + for (auto& module : _modules) { - request_rerender_all(); // TODO(Modules) Only rerender the modules that depend on this Midi channel + if (module->depends_on().midi_channel(midi_channel)) + module->needs_to_rerender_flag().set_dirty(); } } void ModulesGraph::on_last_midi_button_pressed_changed() { - if (_compositing_module.depends_on().last_midi_button_pressed - || std::any_of(_particles_module_nodes.begin(), _particles_module_nodes.end(), [&](auto const& module_node) { return module_node->module.depends_on().last_midi_button_pressed; })) + for (auto& module : _modules) { - request_rerender_all(); // TODO(Modules) Only rerender the modules that depend on this last_midi_button_pressed + if (module->depends_on().last_midi_button_pressed) + module->needs_to_rerender_flag().set_dirty(); } } void ModulesGraph::update_dependencies_from_nodes_graph() { - _compositing_module.update_dependencies_from_nodes_graph(_nodes_editor.graph()); - for (auto const& module_node : _particles_module_nodes) - module_node->module.update_dependencies_from_nodes_graph(_nodes_editor.graph()); + for (auto const& module : _modules) + module->update_dependencies_from_nodes_graph(_nodes_editor.graph()); } void ModulesGraph::debug_show_nodes_and_links_registries_windows(Ui_Ref ui) const @@ -345,19 +433,19 @@ auto ModulesGraph::get_main_node_id() const -> Cool::NodeId const& void ModulesGraph::set_main_node_id(Cool::NodeId const& id) { _main_node_id = id; - regenerate_code_flag().set_dirty(); // Important when calling this function from a Command + rebuild_modules_graph_flag().set_dirty(); // Important when calling this function from a Command } void ModulesGraph::add_node(Cool::NodeId const& id, Node const& node) { _nodes_editor.graph().add_node(id, node); - regenerate_code_flag().set_dirty(); // Important when calling this function from a Command + rebuild_modules_graph_flag().set_dirty(); // Important when calling this function from a Command } void ModulesGraph::add_link(Cool::LinkId const& id, Cool::Link const& link) { _nodes_editor.graph().add_link(id, link); - regenerate_code_flag().set_dirty(); // Important when calling this function from a Command + rebuild_modules_graph_flag().set_dirty(); // Important when calling this function from a Command } void ModulesGraph::remove_node(Cool::NodeId const& id) @@ -366,13 +454,13 @@ void ModulesGraph::remove_node(Cool::NodeId const& id) node.downcast().clear_all_error_messages(); }); _nodes_editor.graph().remove_node(id); - regenerate_code_flag().set_dirty(); // Important when calling this function from a Command + rebuild_modules_graph_flag().set_dirty(); // Important when calling this function from a Command } void ModulesGraph::remove_link(Cool::LinkId const& id) { _nodes_editor.graph().remove_link(id); - regenerate_code_flag().set_dirty(); // Important when calling this function from a Command + rebuild_modules_graph_flag().set_dirty(); // Important when calling this function from a Command } auto ModulesGraph::try_get_node(Cool::NodeId const& id) const -> Node const* @@ -385,7 +473,7 @@ void ModulesGraph::set_node(Cool::NodeId const& id, Node const& value) graph().nodes().with_mutable_ref(id, [&](Cool::Node& node) { node.downcast() = value; }); - regenerate_code_flag().set_dirty(); // Important when calling this function from a Command + rebuild_modules_graph_flag().set_dirty(); // Important when calling this function from a Command } } // namespace Lab diff --git a/src/ModulesGraph/ModulesGraph.h b/src/ModulesGraph/ModulesGraph.h index 56ae82034..915e3e238 100644 --- a/src/ModulesGraph/ModulesGraph.h +++ b/src/ModulesGraph/ModulesGraph.h @@ -3,43 +3,12 @@ #include "Cool/OSC/OSCChannel.h" #include "Cool/View/GizmoManager.h" #include "DirtyFlags.h" +#include "Module/Module.h" #include "Module/ShaderBased/DataToGenerateShaderCode.hpp" -#include "Module_Compositing/Module_Compositing.h" -#include "Module_Particles/Module_Particles.h" #include "Nodes/NodesConfig.h" namespace Lab { -struct ModulesGraphNode { - ModulesGraphNode() = default; - // ~ModulesGraphNode() = default; - - // ModulesGraphNode(const ModulesGraphNode&) = delete; // We disable copying - // ModulesGraphNode& operator=(const ModulesGraphNode&) = delete; // We disable copying - // ModulesGraphNode(ModulesGraphNode&& other) noexcept = default; - // ModulesGraphNode& operator=(ModulesGraphNode&& other) noexcept = default; - - ModulesGraphNode(Module_Particles module, std::string texture_name_in_shader) - : module{std::move(module)} - , texture_name_in_shader{std::move(texture_name_in_shader)} - { - } - - Module_Particles module{}; - std::string texture_name_in_shader{}; - Cool::RenderTarget render_target{}; - -private: - friend class ser20::access; - template - void serialize(Archive& archive) - { - archive( - ser20::make_nvp("Module", module) - ); - } -}; - /// The main class containing all the nodes of the project. /// It is responsible for spawning the various modules as required by the nodes, and knowing the dependencies between them. class ModulesGraph { @@ -47,14 +16,14 @@ class ModulesGraph { ModulesGraph() = default; void update(); - void render(Cool::RenderTarget&, DataToPassToShader const&, DataToGenerateShaderCode const&); + void render(DataToPassToShader const&, DataToGenerateShaderCode const&); void request_rerender_all(); [[nodiscard]] auto is_empty() const -> bool { return _nodes_editor.is_empty(); } [[nodiscard]] auto graph() const -> Cool::NodesGraph const& { return _nodes_editor.graph(); } [[nodiscard]] auto graph() -> Cool::NodesGraph& { return _nodes_editor.graph(); } - [[nodiscard]] auto regenerate_code_flag() const -> Cool::DirtyFlag const& { return _dirty_flags.regenerate_code; } + [[nodiscard]] auto rebuild_modules_graph_flag() const -> Cool::DirtyFlag const& { return _dirty_flags.rebuild; } [[nodiscard]] auto rerender_all_flag() const -> Cool::DirtyFlag const& { return _dirty_flags.rerender; } [[nodiscard]] auto dirty_flags() const -> DirtyFlags const& { return _dirty_flags; } [[nodiscard]] auto nodes_config(Ui_Ref, Cool::AudioManager&, Cool::NodesLibrary const&) const -> NodesConfig; @@ -75,6 +44,8 @@ class ModulesGraph { void imgui_windows(Ui_Ref, Cool::AudioManager&, Cool::NodesLibrary const&) const; void submit_gizmos(Cool::GizmoManager&, CommandExecutor const&, Cool::Camera2D const&); + auto final_texture() const -> Cool::TextureRef; + //---- // These functions are mostly here so that Commands can do their job easily //---- @@ -86,21 +57,26 @@ class ModulesGraph { void remove_link(Cool::LinkId const&); auto try_get_node(Cool::NodeId const&) const -> Node const*; void set_node(Cool::NodeId const&, Node const&); - auto compositing_module() const -> Module_Compositing const& { return _compositing_module; } private: - void create_and_compile_all_modules(Cool::NodeId const& root_node_id, DataToGenerateShaderCode const&); - void render_one_module(Module&, Cool::RenderTarget&, DataToPassToShader const&); - void render_compositing_module(Cool::RenderTarget&, DataToPassToShader const&); - void render_particle_module(Module_Particles&, Cool::RenderTarget&, DataToPassToShader const&); + void recreate_all_modules(Cool::NodeId const& node_id, DataToGenerateShaderCode const&); + auto create_module(Cool::NodeId const& node_id, DataToGenerateShaderCode const&) -> std::shared_ptr; + auto create_module_impl(std::string const& texture_name_in_shader, std::function()> const& make_module) -> std::shared_ptr; + auto create_compositing_module(Cool::NodeId const& node_id, DataToGenerateShaderCode const&) -> std::shared_ptr; + auto create_particles_module(Cool::NodeId const& node_id, NodeDefinition const&, DataToGenerateShaderCode const&) -> std::shared_ptr; + auto create_feedback_loop_module(Cool::NodeId const& node_id, DataToGenerateShaderCode const&) -> std::shared_ptr; + auto create_default_module() -> std::shared_ptr; + + void render_module_ifn(Module&, DataToPassToShader const&); + void check_for_rerender_and_rebuild(DataToPassToShader const&, DataToGenerateShaderCode const&); private: mutable Cool::NodesEditor _nodes_editor{}; mutable Cool::NodeId _main_node_id{}; // TODO(Modules) Rename as _root_node_id? Or _output_node_id? DirtyFlags _dirty_flags{}; - mutable Module_Compositing _compositing_module{}; - std::vector> _particles_module_nodes{}; // TODO(Particles) No need for the unique_ptr (in theory) + std::vector> _modules{}; // TODO(Particles) No need for the unique_ptr (in theory) + std::shared_ptr _root_module{}; // Never null private: // Serialization @@ -109,8 +85,7 @@ class ModulesGraph { void serialize(Archive& archive) { archive( - ser20::make_nvp("Compositing module", _compositing_module), - ser20::make_nvp("Particles module", _particles_module_nodes), + ser20::make_nvp("Modules", _modules), ser20::make_nvp("Dirty flags", _dirty_flags), ser20::make_nvp("Node editor", _nodes_editor), ser20::make_nvp("Main node ID", _main_node_id) diff --git a/src/Project.cpp b/src/Project.cpp index 4ba7314f1..50b1d6d97 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -4,8 +4,8 @@ namespace Lab { Project::Project() - : camera_3D_manager{"3D Camera", modules_graph->rerender_all_flag()} - , camera_2D_manager{"2D Camera", modules_graph->rerender_all_flag()} + : camera_3D_manager{"3D Camera", modules_graph->rerender_all_flag()} // TODO(Modules) Actually, some modules don't depend on the camera, so we shouldn't rerender everything + , camera_2D_manager{"2D Camera", modules_graph->rerender_all_flag()} // TODO(Modules) Actually, some modules don't depend on the camera, so we shouldn't rerender everything { } diff --git a/src/Serialization/impl.cpp b/src/Serialization/impl.cpp index a9d9c248b..2f4a3daab 100644 --- a/src/Serialization/impl.cpp +++ b/src/Serialization/impl.cpp @@ -7,9 +7,14 @@ #include #include "Cool/Serialization/Serialization.h" #include "Dump/coollab_version.h" +#include "Module_Compositing/Module_Compositing.h" +#include "Module_Default/Module_Default.hpp" +#include "Module_FeedbackLoop/Module_FeedbackLoop.hpp" +#include "Module_Particles/Module_Particles.h" #include "SNodesCategoryConfig.h" #include "SNodesClipboard.h" #include "SProject.h" + // #include "ser20/archives/json.hpp" @@ -55,5 +60,7 @@ auto string_to_nodes_clipboard(std::string const& string) -> NodesClipboard } // namespace Lab -SER20_REGISTER_TYPE(Lab::Module_Compositing); // NOLINT -SER20_REGISTER_TYPE(Lab::Module_Particles); // NOLINT \ No newline at end of file +SER20_REGISTER_TYPE(Lab::Module_Compositing); // NOLINT +SER20_REGISTER_TYPE(Lab::Module_Particles); // NOLINT +SER20_REGISTER_TYPE(Lab::Module_FeedbackLoop); // NOLINT +SER20_REGISTER_TYPE(Lab::Module_Default); // NOLINT \ No newline at end of file