#include "app_base.h" #include "imgui.h" #include "gfx/shader_canvas.h" #include "gfx/gl_shader.h" #include "gfx/uniform_parser.h" #include "gfx/uniform_panel.h" #include "gfx/dag_catalog.h" #include "gfx/dag_compile.h" #include "gfx/dag_uniforms.h" #include "gfx/dag_panel.h" #include "gfx/dag_node_editor.h" #include "gfx/dag_palette.h" #include "gfx/dag_node_previews.h" #include "core/fps_overlay.h" #include "seed_shaders.h" #include #include #include #include static fn::gfx::ShaderCanvas g_canvas_code; static fn::gfx::ShaderCanvas g_canvas_dag; static std::string g_source = PLASMA; static std::string g_code_err; static int g_code_err_line = -1; static std::chrono::steady_clock::time_point g_code_last_edit; static bool g_code_dirty = true; static std::vector g_descs; static fn::gfx::UniformStore g_store; static std::vector g_pipeline; static std::string g_dag_glsl; static std::string g_dag_err; static int g_dag_err_line = -1; static bool g_dag_dirty = true; static void compile_code() { auto r = fn::gfx::compile_fragment(g_source); if (r.ok) { g_descs = fn::gfx::parse_uniforms(g_source); fn::gfx::uniforms_sync(g_store, g_descs); fn::gfx::canvas_set_program(g_canvas_code, r.program); g_code_err.clear(); g_code_err_line = -1; } else { g_code_err = r.err_msg; g_code_err_line = r.err_line; } } static void compile_dag() { g_dag_glsl = fn::gfx::compile_dag_to_glsl(g_pipeline); auto r = fn::gfx::compile_fragment(g_dag_glsl); if (r.ok) { fn::gfx::canvas_set_program(g_canvas_dag, r.program); g_dag_err.clear(); g_dag_err_line = -1; } else { g_dag_err = r.err_msg; g_dag_err_line = r.err_line; } } static void mark_code_dirty() { g_code_last_edit = std::chrono::steady_clock::now(); g_code_dirty = true; } static void load_preset(const char* src) { g_source = src; mark_code_dirty(); } static void ensure_dag_default() { // Seed with a Plasma connected to an Output node. if (g_pipeline.empty()) { const fn::gfx::DagNodeDef* plasma = fn::gfx::dag_find("plasma"); if (plasma) { fn::gfx::DagStep s; s.id = "n_plasma"; s.name = plasma->name; s.params = plasma->param_defaults; g_pipeline.push_back(s); } } // Ensure there is always an Output node at the end. bool has_output = false; for (const auto& s : g_pipeline) { const fn::gfx::DagNodeDef* d = fn::gfx::dag_find(s.name); if (d && d->kind == fn::gfx::DagKind::Output) { has_output = true; break; } } if (!has_output) { const fn::gfx::DagNodeDef* out = fn::gfx::dag_find("output"); if (out) { fn::gfx::DagStep s; s.id = "n_output"; s.name = out->name; s.editor_pos_x = 500.0f; if (!g_pipeline.empty()) s.source_ids[0] = g_pipeline.front().id; g_pipeline.push_back(s); } } } static void draw_err(const std::string& msg, int line) { if (msg.empty()) return; ImGui::Separator(); if (line > 0) { ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "line %d: %s", line, msg.c_str()); } else { ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "%s", msg.c_str()); } } static void render() { if (!g_canvas_code.initialized) fn::gfx::canvas_init(g_canvas_code); if (!g_canvas_dag.initialized) fn::gfx::canvas_init(g_canvas_dag); if (g_code_dirty) { auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast(now - g_code_last_edit).count(); if (elapsed > 250) { compile_code(); g_code_dirty = false; } } if (g_dag_dirty) { compile_dag(); g_dag_dirty = false; } ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport()); // --- Code window --- if (ImGui::Begin("Code")) { if (ImGui::Button("Plasma")) { load_preset(PLASMA); } ImGui::SameLine(); if (ImGui::Button("Circle")) { load_preset(CIRCLE); } ImGui::SameLine(); if (ImGui::Button("Checker")) { load_preset(CHECKER); } ImVec2 avail = ImGui::GetContentRegionAvail(); float footer_h = g_code_err.empty() ? 0.0f : ImGui::GetTextLineHeightWithSpacing() + 8.0f; ImVec2 editor_size(avail.x, avail.y - footer_h); char buf[1 << 16]; size_t copy_len = g_source.size() < sizeof(buf) - 1 ? g_source.size() : sizeof(buf) - 1; memcpy(buf, g_source.c_str(), copy_len); buf[copy_len] = '\0'; if (ImGui::InputTextMultiline("##code", buf, sizeof(buf), editor_size, ImGuiInputTextFlags_AllowTabInput)) { g_source = buf; mark_code_dirty(); } draw_err(g_code_err, g_code_err_line); } ImGui::End(); // --- DAG Pipeline window --- if (ImGui::Begin("DAG Pipeline")) { if (fn::gfx::dag_node_editor(g_pipeline)) { g_dag_dirty = true; } draw_err(g_dag_err, g_dag_err_line); } ImGui::End(); // --- Canvas Code --- if (ImGui::Begin("Canvas Code")) { fn::gfx::canvas_render(g_canvas_code, static_cast(ImGui::GetTime()), [](unsigned int program) { fn::gfx::uniforms_apply(g_store, g_descs, program); }); } ImGui::End(); // --- Canvas DAG --- if (ImGui::Begin("Canvas DAG")) { fn::gfx::canvas_render(g_canvas_dag, static_cast(ImGui::GetTime()), [](unsigned int program) { fn::gfx::dag_uniforms_apply(g_pipeline, program); }); } ImGui::End(); // Render per-node previews (only nodes with preview_open=true) if (g_canvas_dag.program) { fn::gfx::dag_previews_render(g_pipeline, g_canvas_dag.program); } // --- Controls window (Code uniforms) --- if (ImGui::Begin("Controls")) { if (g_descs.empty()) { ImGui::TextDisabled("No uniforms declared in Code."); ImGui::TextDisabled("Use // @slider, @color, @toggle, @xy annotations."); } else { fn::gfx::uniforms_panel(g_store, g_descs); } ImGui::Spacing(); fps_overlay(); } ImGui::End(); // --- Functions palette (drag into DAG Pipeline) --- if (ImGui::Begin("Functions")) { fn::gfx::dag_palette(); } ImGui::End(); // --- Generated GLSL window (DAG compiled output, read-only) --- if (ImGui::Begin("Generated GLSL")) { if (g_dag_glsl.empty()) { ImGui::TextDisabled("(DAG not compiled yet)"); } else { ImVec2 avail = ImGui::GetContentRegionAvail(); ImGui::InputTextMultiline("##dag_glsl", const_cast(g_dag_glsl.c_str()), g_dag_glsl.size() + 1, avail, ImGuiInputTextFlags_ReadOnly); } } ImGui::End(); } int main() { ensure_dag_default(); fn::AppConfig cfg; cfg.title = "shaders_lab"; cfg.width = 1600; cfg.height = 900; int rc = fn::run_app(cfg, render); fn::gfx::canvas_destroy(g_canvas_code); fn::gfx::canvas_destroy(g_canvas_dag); fn::gfx::dag_node_editor_destroy(); fn::gfx::dag_previews_destroy(); return rc; }