Add self-feedback system and water shader

Features:
- Self-feedback rendering system for shaders with feedback loops
- Automatic FBO/texture management for feedback
- Metadata parser detects iChannel feedback configuration
- Adaptive render loop (with/without feedback)
- Water shader from Shadertoy (adapted and working)
- Fixed variable initialization issues in shader code

Technical details:
- FBO creation/destruction on shader switch
- Texture binding to iChannel0-3 based on metadata
- Auto-resize feedback buffers on window resize
- Cleanup on exit and shader switch

Files:
- src/main.cpp: Feedback system implementation
- shaders/water.glsl: Water shader with fixes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-16 15:45:18 +01:00
parent 44de2c7013
commit 194726f823
12 changed files with 285 additions and 44 deletions

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 703 KiB

BIN
release/icon/icon.pxd Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,5 @@
// Name: Water // Name: Water
// Author: diatribes // Author: diatribes (FabriceNeyret2)
#version 330 core #version 330 core
precision highp float; precision highp float;
@@ -14,18 +14,22 @@ uniform float iTime;
thanks!! :D thanks!! :D
If it doesn't display correctly, change line 17 "r/r" to "vec3(1)" If it doesn't display correctly, change line 17 "r/r" to "vec3(1)"
*/ */
void mainImage( out vec4 o, vec2 u ) { void mainImage( out vec4 o, vec2 u ) {
float s=.002,i,n; float s=.002, i=0., n; // FIXED: Initialize i=0
vec3 r = vec3(iResolution.xy, iResolution.x/iResolution.y); vec3 r = vec3(iResolution.xy, iResolution.x/iResolution.y);
vec3 p = vec3(0); vec3 p = vec3(0);
u = (u-r.xy/2.)/r.y-.3; u = (u-r.xy/2.)/r.y-.3;
for(o *= i; i++ < 32. && s > .001;o += vec4(5,2,1,0)/max(length(u-.1), 0.001))
for (p += vec3(u*s,s),s = 1. + p.y, o = vec4(0); // FIXED: Initialize output to black
n =.01; n < 1.;n+=n)
for(; i < 32. && s > .001; i++) {
o += vec4(5,2,1,0)/max(length(u-.1), 0.001);
for (p += vec3(u*s,s), s = 1. + p.y, n =.01; n < 1.; n+=n) {
s += abs(dot(sin(p.z+iTime+p / n), vec3(1))) * n*.1; s += abs(dot(sin(p.z+iTime+p / n), vec3(1))) * n*.1;
}
}
o = tanh(o/5e2); o = tanh(o/5e2);
} }

View File

@@ -34,6 +34,32 @@ struct DisplayMonitor {
int refresh_rate = 0; int refresh_rate = 0;
}; };
// Forward declarations of structs
struct ShaderMetadata {
std::string name;
std::string author;
std::string iChannel0; // "BufferA", "BufferB", "none", etc.
std::string iChannel1;
std::string iChannel2;
std::string iChannel3;
};
struct ShaderBuffer {
GLuint program = 0; // Shader program for this buffer
GLuint fbo = 0; // Framebuffer object
GLuint texture = 0; // Output texture
std::string name; // "BufferA", "BufferB", etc.
};
struct ShaderPass {
std::string shaderName; // Base name (e.g., "water")
std::string displayName; // Custom name from metadata
std::string author; // Author from metadata
GLuint imageProgram = 0; // Main image shader program
std::vector<ShaderBuffer> buffers; // BufferA, BufferB, etc.
ShaderMetadata metadata; // iChannel configuration
};
// Globales simplificados (tu proyecto puede integrarlo en clases) // Globales simplificados (tu proyecto puede integrarlo en clases)
static DisplayMonitor display_monitor_; static DisplayMonitor display_monitor_;
static SDL_Window* window_ = nullptr; static SDL_Window* window_ = nullptr;
@@ -52,6 +78,12 @@ static std::vector<ShaderPass> shader_passes_;
static int current_window_width_ = 0; static int current_window_width_ = 0;
static int current_window_height_ = 0; static int current_window_height_ = 0;
// Self-feedback system (for shaders that use their own output as input)
static GLuint feedback_fbo_ = 0;
static GLuint feedback_texture_ = 0;
static bool current_shader_uses_feedback_ = false;
static int feedback_channel_ = -1; // Which iChannel (0-3) is used for feedback
// FPS tracking // FPS tracking
static Uint32 fps_frame_count_ = 0; static Uint32 fps_frame_count_ = 0;
static Uint32 fps_last_update_ticks_ = 0; static Uint32 fps_last_update_ticks_ = 0;
@@ -83,31 +115,6 @@ static bool loadFileToString(const std::filesystem::path& path, std::string& out
return true; return true;
} }
struct ShaderMetadata {
std::string name;
std::string author;
std::string iChannel0; // "BufferA", "BufferB", "none", etc.
std::string iChannel1;
std::string iChannel2;
std::string iChannel3;
};
struct ShaderBuffer {
GLuint program = 0; // Shader program for this buffer
GLuint fbo = 0; // Framebuffer object
GLuint texture = 0; // Output texture
std::string name; // "BufferA", "BufferB", etc.
};
struct ShaderPass {
std::string shaderName; // Base name (e.g., "water")
std::string displayName; // Custom name from metadata
std::string author; // Author from metadata
GLuint imageProgram = 0; // Main image shader program
std::vector<ShaderBuffer> buffers; // BufferA, BufferB, etc.
ShaderMetadata metadata; // iChannel configuration
};
static std::string trimString(const std::string& str) { static std::string trimString(const std::string& str) {
size_t start = str.find_first_not_of(" \t\r\n"); size_t start = str.find_first_not_of(" \t\r\n");
size_t end = str.find_last_not_of(" \t\r\n"); size_t end = str.find_last_not_of(" \t\r\n");
@@ -325,6 +332,171 @@ static bool resizeBuffersIfNeeded(ShaderPass& pass, int width, int height) {
return true; return true;
} }
// ===== Self-Feedback System =====
static bool createFeedbackFBO(int width, int height) {
// Delete existing if any
if (feedback_fbo_ != 0) glDeleteFramebuffers(1, &feedback_fbo_);
if (feedback_texture_ != 0) glDeleteTextures(1, &feedback_texture_);
// Create texture
glGenTextures(1, &feedback_texture_);
glBindTexture(GL_TEXTURE_2D, feedback_texture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Clear to black initially
std::vector<float> black(width * height * 4, 0.0f);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, black.data());
glBindTexture(GL_TEXTURE_2D, 0);
// Create FBO
glGenFramebuffers(1, &feedback_fbo_);
glBindFramebuffer(GL_FRAMEBUFFER, feedback_fbo_);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, feedback_texture_, 0);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (status != GL_FRAMEBUFFER_COMPLETE) {
Logger::error("Feedback FBO creation failed: " + std::to_string(status));
return false;
}
Logger::info("Created feedback FBO (" + std::to_string(width) + "x" + std::to_string(height) + ")");
return true;
}
static void destroyFeedbackFBO() {
if (feedback_fbo_ != 0) {
glDeleteFramebuffers(1, &feedback_fbo_);
feedback_fbo_ = 0;
}
if (feedback_texture_ != 0) {
glDeleteTextures(1, &feedback_texture_);
feedback_texture_ = 0;
}
current_shader_uses_feedback_ = false;
feedback_channel_ = -1;
}
static int detectFeedbackChannel(const ShaderMetadata& metadata) {
// Check which iChannel uses "self" feedback
if (metadata.iChannel0 == "self") return 0;
if (metadata.iChannel1 == "self") return 1;
if (metadata.iChannel2 == "self") return 2;
if (metadata.iChannel3 == "self") return 3;
return -1; // No feedback
}
// ===== Multi-pass Shader Loading =====
// Forward declarations
static GLuint compileShader(GLenum type, const char* src);
static GLuint linkProgram(GLuint vs, GLuint fs);
static std::vector<std::string> findBufferFiles(const std::filesystem::path& imagePath) {
std::vector<std::string> buffers;
std::filesystem::path dir = imagePath.parent_path();
std::string basename = imagePath.stem().stem().string(); // Remove .image.glsl -> get base name
// Check for BufferA, BufferB, BufferC, BufferD
std::vector<std::string> bufferNames = {"bufferA", "bufferB", "bufferC", "bufferD"};
for (const auto& bufName : bufferNames) {
std::filesystem::path bufferPath = dir / (basename + "." + bufName + ".glsl");
if (std::filesystem::exists(bufferPath)) {
buffers.push_back(bufName);
Logger::info("Found buffer: " + bufferPath.string());
}
}
return buffers;
}
static GLuint loadMultiPassShader(const std::filesystem::path& imagePath, ShaderPass& outPass, int width, int height) {
std::string basename = imagePath.stem().stem().string();
outPass.shaderName = basename;
// Load and compile Image shader
std::string imageSrc;
if (!loadFileToString(imagePath, imageSrc)) {
Logger::error("Failed to load image shader: " + imagePath.string());
return 0;
}
// Extract metadata from Image shader
outPass.metadata = extractShaderMetadata(imageSrc);
outPass.displayName = outPass.metadata.name.empty() ? basename : outPass.metadata.name;
outPass.author = outPass.metadata.author;
GLuint vs = compileShader(GL_VERTEX_SHADER, vertexShaderSrc);
GLuint fs = compileShader(GL_FRAGMENT_SHADER, imageSrc.c_str());
if (!vs || !fs) {
if (vs) glDeleteShader(vs);
if (fs) glDeleteShader(fs);
return 0;
}
outPass.imageProgram = linkProgram(vs, fs);
glDeleteShader(vs);
glDeleteShader(fs);
if (!outPass.imageProgram) {
return 0;
}
// Find and load buffer shaders
std::vector<std::string> bufferNames = findBufferFiles(imagePath);
std::filesystem::path dir = imagePath.parent_path();
for (const auto& bufName : bufferNames) {
ShaderBuffer buffer;
buffer.name = bufName;
// Load buffer shader source
std::filesystem::path bufferPath = dir / (basename + "." + bufName + ".glsl");
std::string bufferSrc;
if (!loadFileToString(bufferPath, bufferSrc)) {
Logger::error("Failed to load buffer: " + bufferPath.string());
continue;
}
// Compile buffer shader
GLuint bufVs = compileShader(GL_VERTEX_SHADER, vertexShaderSrc);
GLuint bufFs = compileShader(GL_FRAGMENT_SHADER, bufferSrc.c_str());
if (!bufVs || !bufFs) {
if (bufVs) glDeleteShader(bufVs);
if (bufFs) glDeleteShader(bufFs);
continue;
}
buffer.program = linkProgram(bufVs, bufFs);
glDeleteShader(bufVs);
glDeleteShader(bufFs);
if (!buffer.program) {
continue;
}
// Create FBO and texture for this buffer
if (!createBufferFBO(buffer, width, height)) {
glDeleteProgram(buffer.program);
continue;
}
outPass.buffers.push_back(buffer);
Logger::info("Loaded buffer: " + bufName);
}
Logger::info("Multi-pass shader loaded: " + outPass.displayName + " (" + std::to_string(outPass.buffers.size()) + " buffers)");
return outPass.imageProgram;
}
static void updateWindowTitle() { static void updateWindowTitle() {
if (!window_ || shader_list_.empty()) return; if (!window_ || shader_list_.empty()) return;
@@ -404,7 +576,7 @@ static GLuint loadAndCompileShader(size_t index) {
return 0; return 0;
} }
// Extract custom shader metadata (name and author) from source code // Extract custom shader metadata (name, author, iChannels) from source code
ShaderMetadata metadata = extractShaderMetadata(fragSrc); ShaderMetadata metadata = extractShaderMetadata(fragSrc);
if (!metadata.name.empty()) { if (!metadata.name.empty()) {
shader_names_[index] = metadata.name; shader_names_[index] = metadata.name;
@@ -415,6 +587,14 @@ static GLuint loadAndCompileShader(size_t index) {
Logger::info("Shader author: " + metadata.author); Logger::info("Shader author: " + metadata.author);
} }
// Detect self-feedback
feedback_channel_ = detectFeedbackChannel(metadata);
current_shader_uses_feedback_ = (feedback_channel_ >= 0);
if (current_shader_uses_feedback_) {
Logger::info("Shader uses self-feedback on iChannel" + std::to_string(feedback_channel_));
}
GLuint vs = compileShader(GL_VERTEX_SHADER, vertexShaderSrc); GLuint vs = compileShader(GL_VERTEX_SHADER, vertexShaderSrc);
GLuint fs = compileShader(GL_FRAGMENT_SHADER, fragSrc.c_str()); GLuint fs = compileShader(GL_FRAGMENT_SHADER, fragSrc.c_str());
@@ -527,6 +707,9 @@ void switchShader(int direction) {
glDeleteProgram(current_program_); glDeleteProgram(current_program_);
} }
// Destroy feedback FBO from previous shader
destroyFeedbackFBO();
current_program_ = new_program; current_program_ = new_program;
current_shader_index_ = new_index; current_shader_index_ = new_index;
shader_start_ticks_ = SDL_GetTicks(); shader_start_ticks_ = SDL_GetTicks();
@@ -765,25 +948,76 @@ int main(int argc, char** argv) {
int w, h; int w, h;
SDL_GetWindowSize(window_, &w, &h); SDL_GetWindowSize(window_, &w, &h);
glViewport(0, 0, w, h);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Create/resize feedback FBO if needed
glClear(GL_COLOR_BUFFER_BIT); if (current_shader_uses_feedback_) {
if (feedback_fbo_ == 0 || current_window_width_ != w || current_window_height_ != h) {
createFeedbackFBO(w, h);
current_window_width_ = w;
current_window_height_ = h;
}
}
glUseProgram(current_program_); glUseProgram(current_program_);
// Obtener uniform locations (se recalculan porque el shader puede cambiar) // Obtener uniform locations
GLint locRes = glGetUniformLocation(current_program_, "iResolution"); GLint locRes = glGetUniformLocation(current_program_, "iResolution");
GLint locTime = glGetUniformLocation(current_program_, "iTime"); GLint locTime = glGetUniformLocation(current_program_, "iTime");
if (locRes >= 0) glUniform2f(locRes, float(w), float(h)); float t = (SDL_GetTicks() - shader_start_ticks_) / 1000.0f;
if (locTime >= 0) {
float t = (SDL_GetTicks() - shader_start_ticks_) / 1000.0f;
glUniform1f(locTime, t);
}
glBindVertexArray(vao); // === FEEDBACK RENDERING ===
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); if (current_shader_uses_feedback_) {
glBindVertexArray(0); // Step 1: Bind feedback texture to iChannel
std::string channelName = "iChannel" + std::to_string(feedback_channel_);
GLint locChannel = glGetUniformLocation(current_program_, channelName.c_str());
if (locChannel >= 0) {
glActiveTexture(GL_TEXTURE0 + feedback_channel_);
glBindTexture(GL_TEXTURE_2D, feedback_texture_);
glUniform1i(locChannel, feedback_channel_);
}
// Step 2: Render to feedback FBO
glBindFramebuffer(GL_FRAMEBUFFER, feedback_fbo_);
glViewport(0, 0, w, h);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (locRes >= 0) glUniform2f(locRes, float(w), float(h));
if (locTime >= 0) glUniform1f(locTime, t);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
// Step 3: Render to screen (using the same FBO texture)
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, w, h);
if (locRes >= 0) glUniform2f(locRes, float(w), float(h));
if (locTime >= 0) glUniform1f(locTime, t);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
// Unbind texture
glActiveTexture(GL_TEXTURE0 + feedback_channel_);
glBindTexture(GL_TEXTURE_2D, 0);
} else {
// === NORMAL RENDERING (no feedback) ===
glViewport(0, 0, w, h);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (locRes >= 0) glUniform2f(locRes, float(w), float(h));
if (locTime >= 0) glUniform1f(locTime, t);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
}
SDL_GL_SwapWindow(window_); SDL_GL_SwapWindow(window_);
if (!Options_video.vsync) { if (!Options_video.vsync) {
@@ -798,6 +1032,9 @@ int main(int argc, char** argv) {
glDeleteProgram(current_program_); glDeleteProgram(current_program_);
} }
// Cleanup feedback FBO
destroyFeedbackFBO();
// Cleanup audio // Cleanup audio
if (current_music_) { if (current_music_) {
JA_DeleteMusic(current_music_); JA_DeleteMusic(current_music_);