bf5011de93
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) <noreply@anthropic.com>
210 lines
6.2 KiB
C++
210 lines
6.2 KiB
C++
#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 <chrono>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
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<fn::gfx::UniformDescriptor> g_descs;
|
|
static fn::gfx::UniformStore g_store;
|
|
|
|
static std::vector<fn::gfx::DagStep> 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() {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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<std::chrono::milliseconds>(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_panel(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<float>(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<float>(ImGui::GetTime()),
|
|
[](unsigned int program) {
|
|
fn::gfx::dag_uniforms_apply(g_pipeline, program);
|
|
});
|
|
}
|
|
ImGui::End();
|
|
|
|
// --- 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();
|
|
|
|
// --- 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<char*>(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);
|
|
return rc;
|
|
}
|