feat(shaders_lab): scaffold C++ app with GLSL live-reload canvas
- cpp/functions/gfx: gl_shader, gl_framebuffer, fullscreen_quad, shader_canvas - cpp/apps/shaders_lab: main + 3 seed shaders (plasma, circle, checker) - ImGui docking layout: Code | Canvas | Controls Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
add_imgui_app(shaders_lab
|
||||
main.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/gfx/gl_shader.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/gfx/gl_framebuffer.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/gfx/fullscreen_quad.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/gfx/shader_canvas.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/core/fps_overlay.cpp
|
||||
)
|
||||
target_include_directories(shaders_lab PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
@@ -0,0 +1,115 @@
|
||||
#include "app_base.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include "gfx/shader_canvas.h"
|
||||
#include "gfx/gl_shader.h"
|
||||
#include "core/fps_overlay.h"
|
||||
#include "seed_shaders.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
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 void try_compile() {
|
||||
auto r = fn::gfx::compile_fragment(g_source);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_dirty() {
|
||||
g_last_edit = std::chrono::steady_clock::now();
|
||||
g_dirty = true;
|
||||
}
|
||||
|
||||
static void load_preset(const char* src) {
|
||||
g_source = src;
|
||||
mark_dirty();
|
||||
}
|
||||
|
||||
static void render() {
|
||||
if (!g_canvas.initialized) fn::gfx::canvas_init(g_canvas);
|
||||
|
||||
if (g_dirty) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - g_last_edit).count();
|
||||
if (elapsed > 250) {
|
||||
try_compile();
|
||||
g_dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
// --- Canvas panel ---
|
||||
if (ImGui::Begin("Canvas")) {
|
||||
fn::gfx::canvas_render(g_canvas, static_cast<float>(ImGui::GetTime()));
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
// --- Controls panel ---
|
||||
if (ImGui::Begin("Controls")) {
|
||||
ImGui::TextDisabled("Controls (fase 2)");
|
||||
ImGui::Spacing();
|
||||
fps_overlay();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
int main() {
|
||||
fn::AppConfig cfg;
|
||||
cfg.title = "shaders_lab";
|
||||
cfg.width = 1400;
|
||||
cfg.height = 860;
|
||||
int rc = fn::run_app(cfg, render);
|
||||
fn::gfx::canvas_destroy(g_canvas);
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
// GLSL 330 fragment shader bodies (no #version, no out, no uniform declarations).
|
||||
// compile_fragment() prepends those automatically.
|
||||
|
||||
static const char* PLASMA = R"glsl(
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / u_resolution;
|
||||
float t = u_time * 0.5;
|
||||
|
||||
float v1 = sin(uv.x * 10.0 + t);
|
||||
float v2 = sin(uv.y * 10.0 + t * 1.3);
|
||||
float v3 = sin((uv.x + uv.y) * 10.0 + t * 0.7);
|
||||
float v4 = sin(length(uv - 0.5) * 20.0 - t * 2.0);
|
||||
|
||||
float v = (v1 + v2 + v3 + v4) * 0.25;
|
||||
|
||||
vec3 col = vec3(
|
||||
sin(v * 3.14159 + 0.0) * 0.5 + 0.5,
|
||||
sin(v * 3.14159 + 2.094) * 0.5 + 0.5,
|
||||
sin(v * 3.14159 + 4.188) * 0.5 + 0.5
|
||||
);
|
||||
|
||||
fragColor = vec4(col, 1.0);
|
||||
}
|
||||
)glsl";
|
||||
|
||||
static const char* CIRCLE = R"glsl(
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / u_resolution;
|
||||
vec2 center = vec2(0.5);
|
||||
float t = u_time;
|
||||
|
||||
// Animated center
|
||||
center += vec2(sin(t * 0.7), cos(t * 0.5)) * 0.2;
|
||||
|
||||
float d = length(uv - center);
|
||||
|
||||
// Concentric rings
|
||||
float rings = sin(d * 40.0 - t * 3.0) * 0.5 + 0.5;
|
||||
|
||||
// Radial glow
|
||||
float glow = exp(-d * 4.0);
|
||||
|
||||
vec3 col = mix(
|
||||
vec3(0.05, 0.1, 0.3),
|
||||
vec3(0.2, 0.7, 1.0),
|
||||
rings * glow + glow * 0.4
|
||||
);
|
||||
|
||||
fragColor = vec4(col, 1.0);
|
||||
}
|
||||
)glsl";
|
||||
|
||||
static const char* CHECKER = R"glsl(
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / u_resolution;
|
||||
float t = u_time;
|
||||
|
||||
// Animated scale and rotation
|
||||
float scale = 8.0 + sin(t * 0.4) * 3.0;
|
||||
float angle = t * 0.2;
|
||||
float ca = cos(angle), sa = sin(angle);
|
||||
vec2 p = uv - 0.5;
|
||||
p = vec2(ca * p.x - sa * p.y, sa * p.x + ca * p.y);
|
||||
p = p * scale + 0.5;
|
||||
|
||||
vec2 cell = floor(p);
|
||||
float checker = mod(cell.x + cell.y, 2.0);
|
||||
|
||||
// Color gradient per cell
|
||||
float hue = fract((cell.x + cell.y) * 0.1 + t * 0.05);
|
||||
vec3 col_a = vec3(hue, 0.7, 0.9);
|
||||
vec3 col_b = vec3(fract(hue + 0.5), 0.5, 0.7);
|
||||
|
||||
// Simple HSV to RGB
|
||||
vec3 col = mix(col_b, col_a, checker);
|
||||
// hue is already [0,1], apply saturation/value manually
|
||||
float h = col.x * 6.0;
|
||||
int i = int(h);
|
||||
float f = h - float(i);
|
||||
float p2 = col.z * (1.0 - col.y);
|
||||
float q2 = col.z * (1.0 - col.y * f);
|
||||
float t2 = col.z * (1.0 - col.y * (1.0 - f));
|
||||
vec3 rgb;
|
||||
if (i == 0) rgb = vec3(col.z, t2, p2);
|
||||
else if (i == 1) rgb = vec3(q2, col.z, p2);
|
||||
else if (i == 2) rgb = vec3(p2, col.z, t2);
|
||||
else if (i == 3) rgb = vec3(p2, q2, col.z);
|
||||
else if (i == 4) rgb = vec3(t2, p2, col.z);
|
||||
else rgb = vec3(col.z, p2, q2);
|
||||
|
||||
fragColor = vec4(rgb, 1.0);
|
||||
}
|
||||
)glsl";
|
||||
Reference in New Issue
Block a user