Files
fn_registry/cpp/functions/gfx/dag_node_previews.cpp
egutierrez ab3115ce99 feat(shaders_lab): per-node preview thumbnails + double-rclick delete node
- DagStep: preview_open flag (default false).
- dag_compile: emit `uniform int u_preview_target` and a series of
  early-return branches at the start of fragColor selection. -1 (default)
  falls through to the real Output-driven fragColor.
- dag_node_previews (new fn): per-node FBO keyed by editor_uid, lazy
  created. Renders each node with preview_open=true to its FBO by
  setting u_preview_target = step index. Texture exposed via
  dag_preview_texture(uid) for ImGui::Image.
- dag_node_editor: small toggle button "[+] preview"/"[-] preview" in
  each non-Output node; when open, ImGui::Image(96x64, V-flipped).
- dag_node_editor: double right-click on hovered node deletes it
  (Output is protected).
- main.cpp: dag_previews_render after Canvas DAG; dag_previews_destroy
  on shutdown.

Single GL program drives both the canvas and all thumbnails — moving
sliders never recompiles, only the topology change does.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:06:50 +02:00

103 lines
3.0 KiB
C++

#include "gfx/gl_loader.h"
#include "gfx/dag_node_previews.h"
#include "gfx/dag_catalog.h"
#include "gfx/gl_framebuffer.h"
#include "gfx/fullscreen_quad.h"
#include <unordered_map>
namespace fn::gfx {
struct PreviewSlot {
Framebuffer fb;
bool inited = false;
};
static std::unordered_map<unsigned, PreviewSlot> s_slots;
static Quad s_quad;
static bool s_quad_init = false;
static PreviewSlot& slot_for(unsigned uid) {
auto& slot = s_slots[uid];
if (!slot.inited) {
fb_init(slot.fb);
slot.inited = true;
}
return slot;
}
void dag_previews_render(const std::vector<DagStep>& pipeline,
unsigned program,
int width, int height) {
if (!program || pipeline.empty()) return;
if (!s_quad_init) {
quad_init(s_quad);
s_quad_init = true;
}
// Save GL state we'll touch
GLint prev_fbo = 0;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo);
GLint prev_vp[4];
glGetIntegerv(GL_VIEWPORT, prev_vp);
GLint prev_program = 0;
glGetIntegerv(GL_CURRENT_PROGRAM, &prev_program);
glUseProgram(program);
GLint loc_target = glGetUniformLocation(program, "u_preview_target");
GLint loc_resolution = glGetUniformLocation(program, "u_resolution");
GLint loc_time = glGetUniformLocation(program, "u_time");
if (loc_resolution >= 0) {
glUniform2f(loc_resolution, static_cast<float>(width), static_cast<float>(height));
}
// u_time and u_params are already set by the main canvas pass for this frame.
for (int i = 0; i < static_cast<int>(pipeline.size()); ++i) {
const DagStep& step = pipeline[static_cast<size_t>(i)];
if (!step.preview_open) continue;
const DagNodeDef* def = dag_find(step.name);
if (!def || def->kind == DagKind::Output) continue;
PreviewSlot& slot = slot_for(step.editor_uid);
fb_resize(slot.fb, width, height);
glBindFramebuffer(GL_FRAMEBUFFER, slot.fb.fbo);
glViewport(0, 0, width, height);
if (loc_target >= 0) glUniform1i(loc_target, i);
if (loc_resolution >= 0) glUniform2f(loc_resolution, static_cast<float>(width), static_cast<float>(height));
quad_draw(s_quad);
}
// Reset preview target so the main canvas pass renders the real Output
if (loc_target >= 0) glUniform1i(loc_target, -1);
// Restore state
glUseProgram(static_cast<GLuint>(prev_program));
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(prev_fbo));
glViewport(prev_vp[0], prev_vp[1], prev_vp[2], prev_vp[3]);
if (loc_time >= 0) {} // silence unused
}
unsigned dag_preview_texture(unsigned editor_uid) {
auto it = s_slots.find(editor_uid);
if (it == s_slots.end()) return 0;
return it->second.fb.tex;
}
void dag_previews_destroy() {
for (auto& kv : s_slots) {
fb_destroy(kv.second.fb);
}
s_slots.clear();
if (s_quad_init) {
quad_destroy(s_quad);
s_quad_init = false;
}
}
} // namespace fn::gfx