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:
Binary file not shown.
BIN
release/icon.ico
BIN
release/icon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 137 KiB |
BIN
release/icon.png
BIN
release/icon.png
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
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.
@@ -1,5 +1,5 @@
|
||||
// Name: Water
|
||||
// Author: diatribes
|
||||
// Author: diatribes (FabriceNeyret2)
|
||||
#version 330 core
|
||||
precision highp float;
|
||||
|
||||
@@ -14,18 +14,22 @@ uniform float iTime;
|
||||
thanks!! :D
|
||||
|
||||
If it doesn't display correctly, change line 17 "r/r" to "vec3(1)"
|
||||
|
||||
*/
|
||||
|
||||
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 p = vec3(0);
|
||||
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,
|
||||
n =.01; n < 1.;n+=n)
|
||||
|
||||
o = vec4(0); // FIXED: Initialize output to black
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
o = tanh(o/5e2);
|
||||
}
|
||||
|
||||
309
src/main.cpp
309
src/main.cpp
@@ -34,6 +34,32 @@ struct DisplayMonitor {
|
||||
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)
|
||||
static DisplayMonitor display_monitor_;
|
||||
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_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
|
||||
static Uint32 fps_frame_count_ = 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
size_t start = str.find_first_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;
|
||||
}
|
||||
|
||||
// ===== 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() {
|
||||
if (!window_ || shader_list_.empty()) return;
|
||||
|
||||
@@ -404,7 +576,7 @@ static GLuint loadAndCompileShader(size_t index) {
|
||||
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);
|
||||
if (!metadata.name.empty()) {
|
||||
shader_names_[index] = metadata.name;
|
||||
@@ -415,6 +587,14 @@ static GLuint loadAndCompileShader(size_t index) {
|
||||
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 fs = compileShader(GL_FRAGMENT_SHADER, fragSrc.c_str());
|
||||
|
||||
@@ -527,6 +707,9 @@ void switchShader(int direction) {
|
||||
glDeleteProgram(current_program_);
|
||||
}
|
||||
|
||||
// Destroy feedback FBO from previous shader
|
||||
destroyFeedbackFBO();
|
||||
|
||||
current_program_ = new_program;
|
||||
current_shader_index_ = new_index;
|
||||
shader_start_ticks_ = SDL_GetTicks();
|
||||
@@ -765,26 +948,77 @@ int main(int argc, char** argv) {
|
||||
|
||||
int w, h;
|
||||
SDL_GetWindowSize(window_, &w, &h);
|
||||
|
||||
// Create/resize feedback FBO if needed
|
||||
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_);
|
||||
|
||||
// Obtener uniform locations
|
||||
GLint locRes = glGetUniformLocation(current_program_, "iResolution");
|
||||
GLint locTime = glGetUniformLocation(current_program_, "iTime");
|
||||
|
||||
float t = (SDL_GetTicks() - shader_start_ticks_) / 1000.0f;
|
||||
|
||||
// === FEEDBACK RENDERING ===
|
||||
if (current_shader_uses_feedback_) {
|
||||
// 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);
|
||||
|
||||
glUseProgram(current_program_);
|
||||
|
||||
// Obtener uniform locations (se recalculan porque el shader puede cambiar)
|
||||
GLint locRes = glGetUniformLocation(current_program_, "iResolution");
|
||||
GLint locTime = glGetUniformLocation(current_program_, "iTime");
|
||||
|
||||
if (locRes >= 0) glUniform2f(locRes, float(w), float(h));
|
||||
if (locTime >= 0) {
|
||||
float t = (SDL_GetTicks() - shader_start_ticks_) / 1000.0f;
|
||||
glUniform1f(locTime, t);
|
||||
}
|
||||
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_);
|
||||
if (!Options_video.vsync) {
|
||||
SDL_Delay(1); // Prevent CPU spinning when vsync is off
|
||||
@@ -798,6 +1032,9 @@ int main(int argc, char** argv) {
|
||||
glDeleteProgram(current_program_);
|
||||
}
|
||||
|
||||
// Cleanup feedback FBO
|
||||
destroyFeedbackFBO();
|
||||
|
||||
// Cleanup audio
|
||||
if (current_music_) {
|
||||
JA_DeleteMusic(current_music_);
|
||||
|
||||
Reference in New Issue
Block a user