#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 "core/fps_overlay.h" #include "seed_shaders.h" #include #include #include enum class AppMode { Code, Dag }; static AppMode g_mode = AppMode::Code; static fn::gfx::ShaderCanvas g_canvas; static std::string g_source = PLASMA; static std::string g_last_err; static int g_last_err_line = -1; static std::chrono::steady_clock::time_point g_last_edit; static bool g_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 void try_compile() { if (g_mode == AppMode::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, r.program); g_last_err.clear(); g_last_err_line = -1; } else { g_last_err = r.err_msg; g_last_err_line = r.err_line; } } else { g_dag_glsl = fn::gfx::compile_dag_to_glsl(g_pipeline); auto r = fn::gfx::compile_fragment(g_dag_glsl); if (r.ok) { g_descs.clear(); g_store.values.clear(); fn::gfx::canvas_set_program(g_canvas, r.program); g_last_err.clear(); g_last_err_line = -1; } else { g_last_err = r.err_msg; g_last_err_line = r.err_line; } } } static void mark_dirty() { g_last_edit = std::chrono::steady_clock::now(); g_dirty = true; } static void load_preset(const char* src) { g_source = src; mark_dirty(); } static void ensure_dag_default() { if (g_pipeline.empty()) { const fn::gfx::DagNodeDef* def = fn::gfx::dag_find("plasma"); if (def) { fn::gfx::DagStep step; step.id = "n_default"; step.name = def->name; step.params = def->param_defaults; g_pipeline.push_back(step); mark_dirty(); } } } static void render() { if (!g_canvas.initialized) fn::gfx::canvas_init(g_canvas); if (g_dirty) { auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast(now - g_last_edit).count(); if (elapsed > 250) { try_compile(); g_dirty = false; } } ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport()); // --- Left panel: Code or DAG --- const char* panel_title = (g_mode == AppMode::Code) ? "Code" : "DAG"; if (ImGui::Begin(panel_title)) { // Mode toggle topbar { bool code_active = (g_mode == AppMode::Code); bool dag_active = (g_mode == AppMode::Dag); if (code_active) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.5f, 0.8f, 1.0f)); if (ImGui::Button("Code")) { if (g_mode != AppMode::Code) { g_mode = AppMode::Code; mark_dirty(); } } if (code_active) ImGui::PopStyleColor(); ImGui::SameLine(); if (dag_active) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.5f, 0.8f, 1.0f)); if (ImGui::Button("DAG")) { if (g_mode != AppMode::Dag) { g_mode = AppMode::Dag; ensure_dag_default(); mark_dirty(); } } if (dag_active) ImGui::PopStyleColor(); ImGui::Separator(); } if (g_mode == AppMode::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_height = g_last_err.empty() ? 0.0f : ImGui::GetTextLineHeightWithSpacing() + 8.0f; ImVec2 editor_size(avail.x, avail.y - footer_height); 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'; ImGui::PushFont(nullptr); if (ImGui::InputTextMultiline("##code", buf, sizeof(buf), editor_size, ImGuiInputTextFlags_AllowTabInput)) { g_source = buf; mark_dirty(); } ImGui::PopFont(); if (!g_last_err.empty()) { ImGui::Separator(); if (g_last_err_line > 0) { ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "line %d: %s", g_last_err_line, g_last_err.c_str()); } else { ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "%s", g_last_err.c_str()); } } } else { // DAG mode bool topo_changed = fn::gfx::dag_panel(g_pipeline); if (topo_changed) { mark_dirty(); } if (!g_last_err.empty()) { ImGui::Separator(); if (g_last_err_line > 0) { ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "line %d: %s", g_last_err_line, g_last_err.c_str()); } else { ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "%s", g_last_err.c_str()); } } } } ImGui::End(); // --- Canvas panel --- if (ImGui::Begin("Canvas")) { if (g_mode == AppMode::Code) { fn::gfx::canvas_render(g_canvas, static_cast(ImGui::GetTime()), [](unsigned int program) { fn::gfx::uniforms_apply(g_store, g_descs, program); }); } else { fn::gfx::canvas_render(g_canvas, static_cast(ImGui::GetTime()), [](unsigned int program) { fn::gfx::dag_uniforms_apply(g_pipeline, program); }); } } ImGui::End(); // --- Controls panel --- if (ImGui::Begin("Controls")) { if (g_mode == AppMode::Code) { fn::gfx::uniforms_panel(g_store, g_descs); } else { // Show generated GLSL read-only if (ImGui::CollapsingHeader("Generated GLSL")) { ImVec2 avail = ImGui::GetContentRegionAvail(); ImGui::InputTextMultiline("##dag_glsl", const_cast(g_dag_glsl.c_str()), g_dag_glsl.size() + 1, ImVec2(avail.x, std::min(avail.y, 400.0f)), ImGuiInputTextFlags_ReadOnly); } } ImGui::Spacing(); fps_overlay(); } ImGui::End(); } int main() { fn::AppConfig cfg; cfg.title = "shaders_lab"; cfg.width = 1400; cfg.height = 860; int rc = fn::run_app(cfg, render); fn::gfx::canvas_destroy(g_canvas); return rc; }