feat(shaders_lab): DAG pipeline mode with node catalog
- cpp/functions/gfx/dag_types: DagStep, DagNodeDef, DagControl (header-only) - cpp/functions/gfx/dag_catalog: 10 hardcoded nodes (4 gen, 3 op, 3 blend) ported from shader-dag-blends.jsx - cpp/functions/gfx/dag_compile: pipeline → GLSL 330 core with fan-in via source_id - cpp/functions/gfx/dag_uniforms: upload u_params[16] via glUniform4fv - cpp/functions/gfx/dag_panel: ImGui pipeline editor (add/remove/reorder/controls) - main.cpp: Code/DAG mode toggle, per-mode compile path and uniforms - gl_loader: +glUniform4fv - rebuild Windows .exe Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+146
-45
@@ -5,6 +5,10 @@
|
||||
#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"
|
||||
|
||||
@@ -12,6 +16,9 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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;
|
||||
@@ -20,18 +27,35 @@ static std::chrono::steady_clock::time_point g_last_edit;
|
||||
static bool g_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 void try_compile() {
|
||||
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;
|
||||
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_last_err = r.err_msg;
|
||||
g_last_err_line = r.err_line;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +69,20 @@ static void load_preset(const char* 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);
|
||||
|
||||
@@ -59,38 +97,82 @@ static void render() {
|
||||
|
||||
ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());
|
||||
|
||||
// --- Code panel ---
|
||||
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_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); // use default monospace-ish font
|
||||
if (ImGui::InputTextMultiline("##code", buf, sizeof(buf), editor_size,
|
||||
ImGuiInputTextFlags_AllowTabInput)) {
|
||||
g_source = buf;
|
||||
mark_dirty();
|
||||
}
|
||||
ImGui::PopFont();
|
||||
|
||||
if (!g_last_err.empty()) {
|
||||
// --- 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_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());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,16 +180,35 @@ static void render() {
|
||||
|
||||
// --- Canvas panel ---
|
||||
if (ImGui::Begin("Canvas")) {
|
||||
fn::gfx::canvas_render(g_canvas, static_cast<float>(ImGui::GetTime()),
|
||||
[](unsigned int program) {
|
||||
fn::gfx::uniforms_apply(g_store, g_descs, program);
|
||||
});
|
||||
if (g_mode == AppMode::Code) {
|
||||
fn::gfx::canvas_render(g_canvas, static_cast<float>(ImGui::GetTime()),
|
||||
[](unsigned int program) {
|
||||
fn::gfx::uniforms_apply(g_store, g_descs, program);
|
||||
});
|
||||
} else {
|
||||
fn::gfx::canvas_render(g_canvas, static_cast<float>(ImGui::GetTime()),
|
||||
[](unsigned int program) {
|
||||
fn::gfx::dag_uniforms_apply(g_pipeline, program);
|
||||
});
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
// --- Controls panel ---
|
||||
if (ImGui::Begin("Controls")) {
|
||||
fn::gfx::uniforms_panel(g_store, g_descs);
|
||||
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<char*>(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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user