From bf5011de93401b410b7a31a1b60a4f954ff9c9a3 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Fri, 24 Apr 2026 21:50:29 +0200 Subject: [PATCH] feat(shaders_lab): two simultaneous canvases (Code + DAG) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Canvas Code and Canvas DAG are now independent windows, each with its own ShaderCanvas, FBO and compiled program. Both render every frame in parallel. No more focus-based recompile — each source compiles when its own content changes. Co-Authored-By: Claude Opus 4.7 (1M context) --- cpp/apps/shaders_lab/main.cpp | 163 ++++++++++++++++------------------ 1 file changed, 77 insertions(+), 86 deletions(-) diff --git a/cpp/apps/shaders_lab/main.cpp b/cpp/apps/shaders_lab/main.cpp index a177da63..bb2cafa8 100644 --- a/cpp/apps/shaders_lab/main.cpp +++ b/cpp/apps/shaders_lab/main.cpp @@ -13,67 +13,62 @@ #include "seed_shaders.h" #include -#include #include #include #include -enum class Source { Code, Dag }; +static fn::gfx::ShaderCanvas g_canvas_code; +static fn::gfx::ShaderCanvas g_canvas_dag; -static Source g_active = Source::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::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 try_compile() { - if (g_active == Source::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; - } +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_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, 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; - } + g_code_err = r.err_msg; + g_code_err_line = r.err_line; } } -static void mark_dirty() { - g_last_edit = std::chrono::steady_clock::now(); - g_dirty = true; +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 set_active(Source s) { - if (g_active != s) { - g_active = s; - try_compile(); - } +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_dirty(); + mark_code_dirty(); } static void ensure_dag_default() { @@ -89,47 +84,46 @@ static void ensure_dag_default() { } } -static void draw_err_footer() { - if (g_last_err.empty()) return; +static void draw_err(const std::string& msg, int line) { + if (msg.empty()) return; 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()); + 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", g_last_err.c_str()); + ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "%s", msg.c_str()); } } static void render() { - if (!g_canvas.initialized) fn::gfx::canvas_init(g_canvas); + 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_dirty) { + if (g_code_dirty) { auto now = std::chrono::steady_clock::now(); - auto elapsed = std::chrono::duration_cast(now - g_last_edit).count(); + auto elapsed = std::chrono::duration_cast(now - g_code_last_edit).count(); if (elapsed > 250) { - try_compile(); - g_dirty = false; + 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::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) { - set_active(Source::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); } - bool has_err_here = !g_last_err.empty() && g_active == Source::Code; ImVec2 avail = ImGui::GetContentRegionAvail(); - float footer_height = has_err_here ? ImGui::GetTextLineHeightWithSpacing() + 8.0f : 0.0f; - ImVec2 editor_size(avail.x, avail.y - footer_height); + 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; @@ -139,41 +133,37 @@ static void render() { if (ImGui::InputTextMultiline("##code", buf, sizeof(buf), editor_size, ImGuiInputTextFlags_AllowTabInput)) { g_source = buf; - mark_dirty(); + mark_code_dirty(); } - if (has_err_here) draw_err_footer(); + draw_err(g_code_err, g_code_err_line); } ImGui::End(); // --- DAG Pipeline window --- if (ImGui::Begin("DAG Pipeline")) { - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) { - set_active(Source::Dag); + if (fn::gfx::dag_panel(g_pipeline)) { + g_dag_dirty = true; } - bool topo_changed = fn::gfx::dag_panel(g_pipeline); - if (topo_changed) mark_dirty(); - - if (!g_last_err.empty() && g_active == Source::Dag) draw_err_footer(); + draw_err(g_dag_err, g_dag_err_line); } ImGui::End(); - // --- Canvas window --- - char canvas_title[64]; - snprintf(canvas_title, sizeof(canvas_title), "Canvas [%s]###canvas", - g_active == Source::Code ? "Code" : "DAG"); - if (ImGui::Begin(canvas_title)) { - if (g_active == Source::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); - }); - } + // --- 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(); @@ -193,7 +183,7 @@ static void render() { // --- Generated GLSL window (DAG compiled output, read-only) --- if (ImGui::Begin("Generated GLSL")) { if (g_dag_glsl.empty()) { - ImGui::TextDisabled("(DAG not compiled yet — focus the DAG Pipeline window)"); + ImGui::TextDisabled("(DAG not compiled yet)"); } else { ImVec2 avail = ImGui::GetContentRegionAvail(); ImGui::InputTextMultiline("##dag_glsl", @@ -210,9 +200,10 @@ int main() { ensure_dag_default(); fn::AppConfig cfg; cfg.title = "shaders_lab"; - cfg.width = 1400; - cfg.height = 860; + cfg.width = 1600; + cfg.height = 900; int rc = fn::run_app(cfg, render); - fn::gfx::canvas_destroy(g_canvas); + fn::gfx::canvas_destroy(g_canvas_code); + fn::gfx::canvas_destroy(g_canvas_dag); return rc; }