ac65791663
- 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>
103 lines
3.0 KiB
C++
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
|