Files
manual_slop/src/shader_manager.py
Ed_ aed461ef28 fix(shader): Add proper VAO setup for OpenGL 3.3 core profile
- Create _create_quad_vao() method that creates VAO with vertex buffer
- Use explicit vertex attributes (a_position, a_texcoord) in shaders
- Bind VAO before glDrawArrays calls
- Use ctypes for proper buffer sizing
2026-03-13 21:35:23 -04:00

475 lines
15 KiB
Python

import OpenGL.GL as gl
class ShaderManager:
def __init__(self):
self.program = None
self.bg_program = None
self.pp_program = None
def compile_shader(self, vertex_src: str, fragment_src: str) -> int:
program = gl.glCreateProgram()
def _compile(src, shader_type):
shader = gl.glCreateShader(shader_type)
gl.glShaderSource(shader, src)
gl.glCompileShader(shader)
if not gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS):
info_log = gl.glGetShaderInfoLog(shader)
if hasattr(info_log, "decode"):
info_log = info_log.decode()
raise RuntimeError(f"Shader compilation failed: {info_log}")
return shader
vert_shader = _compile(vertex_src, gl.GL_VERTEX_SHADER)
frag_shader = _compile(fragment_src, gl.GL_FRAGMENT_SHADER)
gl.glAttachShader(program, vert_shader)
gl.glAttachShader(program, frag_shader)
gl.glLinkProgram(program)
if not gl.glGetProgramiv(program, gl.GL_LINK_STATUS):
info_log = gl.glGetProgramInfoLog(program)
if hasattr(info_log, "decode"):
info_log = info_log.decode()
raise RuntimeError(f"Program linking failed: {info_log}")
gl.glDeleteShader(vert_shader)
gl.glDeleteShader(frag_shader)
self.program = program
return program
def update_uniforms(self, uniforms: dict):
if self.program is None:
return
for name, value in uniforms.items():
loc = gl.glGetUniformLocation(self.program, name)
if loc == -1:
continue
if isinstance(value, float):
gl.glUniform1f(loc, value)
elif isinstance(value, int):
gl.glUniform1i(loc, value)
elif isinstance(value, (list, tuple)):
if len(value) == 2:
gl.glUniform2f(loc, value[0], value[1])
elif len(value) == 3:
gl.glUniform3f(loc, value[0], value[1], value[2])
elif len(value) == 4:
gl.glUniform4f(loc, value[0], value[1], value[2], value[3])
def setup_background_shader(self):
vertex_src = """
#version 330 core
const vec2 positions[4] = vec2[](
vec2(-1.0, -1.0),
vec2( 1.0, -1.0),
vec2(-1.0, 1.0),
vec2( 1.0, 1.0)
);
void main() {
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
}
"""
fragment_src = """
#version 330 core
uniform float u_time;
uniform vec2 u_resolution;
out vec4 FragColor;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
vec3 col = 0.5 + 0.5 * cos(u_time + uv.xyx + vec3(0, 2, 4));
FragColor = vec4(col, 1.0);
}
"""
self.bg_program = self.compile_shader(vertex_src, fragment_src)
def render_background(self, width, height, time):
if not self.bg_program:
return
gl.glUseProgram(self.bg_program)
u_time_loc = gl.glGetUniformLocation(self.bg_program, "u_time")
if u_time_loc != -1:
gl.glUniform1f(u_time_loc, float(time))
u_res_loc = gl.glGetUniformLocation(self.bg_program, "u_resolution")
if u_res_loc != -1:
gl.glUniform2f(u_res_loc, float(width), float(height))
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
gl.glUseProgram(0)
def setup_post_process_shader(self):
vertex_src = """
#version 330 core
const vec2 positions[4] = vec2[](
vec2(-1.0, -1.0),
vec2( 1.0, -1.0),
vec2(-1.0, 1.0),
vec2( 1.0, 1.0)
);
const vec2 uvs[4] = vec2[](
vec2(0.0, 0.0),
vec2(1.0, 0.0),
vec2(0.0, 1.0),
vec2(1.0, 1.0)
);
out vec2 v_uv;
void main() {
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
v_uv = uvs[gl_VertexID];
}
"""
fragment_src = """
#version 330 core
in vec2 v_uv;
uniform sampler2D u_texture;
uniform float u_time;
out vec4 FragColor;
void main() {
vec4 color = texture(u_texture, v_uv);
float scanline = sin(v_uv.y * 800.0 + u_time * 2.0) * 0.04;
color.rgb -= scanline;
FragColor = color;
}
"""
self.pp_program = self.compile_shader(vertex_src, fragment_src)
def render_post_process(self, texture_id, width, height, time):
if not self.pp_program:
return
gl.glUseProgram(self.pp_program)
gl.glActiveTexture(gl.GL_TEXTURE0)
gl.glBindTexture(gl.GL_TEXTURE_2D, texture_id)
u_tex_loc = gl.glGetUniformLocation(self.pp_program, "u_texture")
if u_tex_loc != -1:
gl.glUniform1i(u_tex_loc, 0)
u_time_loc = gl.glGetUniformLocation(self.pp_program, "u_time")
if u_time_loc != -1:
gl.glUniform1f(u_time_loc, float(time))
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
class BlurPipeline:
def __init__(self):
self.scene_fbo: int | None = None
self.scene_tex: int | None = None
self.blur_fbo_a: int | None = None
self.blur_tex_a: int | None = None
self.blur_fbo_b: int | None = None
self.blur_tex_b: int | None = None
self.h_blur_program: int | None = None
self.v_blur_program: int | None = None
self.deepsea_program: int | None = None
self._quad_vao: int | None = None
self._fb_width: int = 0
self._fb_height: int = 0
self._fb_scale: int = 1
def _compile_shader(self, vertex_src: str, fragment_src: str) -> int:
program = gl.glCreateProgram()
def _compile(src, shader_type):
shader = gl.glCreateShader(shader_type)
gl.glShaderSource(shader, src)
gl.glCompileShader(shader)
if not gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS):
info_log = gl.glGetShaderInfoLog(shader)
if hasattr(info_log, "decode"):
info_log = info_log.decode()
raise RuntimeError(f"Shader compilation failed: {info_log}")
return shader
vert_shader = _compile(vertex_src, gl.GL_VERTEX_SHADER)
frag_shader = _compile(fragment_src, gl.GL_FRAGMENT_SHADER)
gl.glAttachShader(program, vert_shader)
gl.glAttachShader(program, frag_shader)
gl.glLinkProgram(program)
if not gl.glGetProgramiv(program, gl.GL_LINK_STATUS):
info_log = gl.glGetProgramInfoLog(program)
if hasattr(info_log, "decode"):
info_log = info_log.decode()
raise RuntimeError(f"Program linking failed: {info_log}")
gl.glDeleteShader(vert_shader)
gl.glDeleteShader(frag_shader)
return program
def _create_fbo(self, width: int, height: int) -> tuple[int, int]:
if width <= 0 or height <= 0:
raise ValueError(f"Invalid FBO dimensions: {width}x{height}")
tex = gl.glGenTextures(1)
gl.glBindTexture(gl.GL_TEXTURE_2D, tex)
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA8, width, height, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, None)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
fbo = gl.glGenFramebuffers(1)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, fbo)
gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, tex, 0)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
return fbo, tex
def _create_quad_vao(self) -> int:
import ctypes
vao = gl.glGenVertexArrays(1)
gl.glBindVertexArray(vao)
vertices = (ctypes.c_float * 16)(
-1.0, -1.0, 0.0, 0.0,
1.0, -1.0, 1.0, 0.0,
-1.0, 1.0, 0.0, 1.0,
1.0, 1.0, 1.0, 1.0
)
vbo = gl.glGenBuffers(1)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo)
gl.glBufferData(gl.GL_ARRAY_BUFFER, ctypes.sizeof(vertices), vertices, gl.GL_STATIC_DRAW)
gl.glEnableVertexAttribArray(0)
gl.glVertexAttribPointer(0, 2, gl.GL_FLOAT, gl.GL_FALSE, 16, None)
gl.glEnableVertexAttribArray(1)
gl.glVertexAttribPointer(1, 2, gl.GL_FLOAT, gl.GL_FALSE, 16, ctypes.c_void_p(8))
gl.glBindVertexArray(0)
return vao
def setup_fbos(self, width: int, height: int, fb_scale: float = 1.0):
scale = max(1, int(fb_scale))
blur_w = max(1, (width * scale) // 4)
blur_h = max(1, (height * scale) // 4)
self._fb_width = blur_w
self._fb_height = blur_h
self._fb_scale = scale
scene_w = width * scale
scene_h = height * scale
self.scene_fbo, self.scene_tex = self._create_fbo(scene_w, scene_h)
self.blur_fbo_a, self.blur_tex_a = self._create_fbo(blur_w, blur_h)
self.blur_fbo_b, self.blur_tex_b = self._create_fbo(blur_w, blur_h)
def compile_blur_shaders(self):
vert_src = """
#version 330 core
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texcoord;
out vec2 v_uv;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_uv = a_texcoord;
}
"""
h_frag_src = """
#version 330 core
in vec2 v_uv;
uniform sampler2D u_texture;
uniform vec2 u_texel_size;
out vec4 FragColor;
void main() {
vec2 offset = vec2(u_texel_size.x, 0.0);
vec4 sum = vec4(0.0);
sum += texture(u_texture, v_uv - offset * 6.0) * 0.0152;
sum += texture(u_texture, v_uv - offset * 5.0) * 0.0300;
sum += texture(u_texture, v_uv - offset * 4.0) * 0.0525;
sum += texture(u_texture, v_uv - offset * 3.0) * 0.0812;
sum += texture(u_texture, v_uv - offset * 2.0) * 0.1110;
sum += texture(u_texture, v_uv - offset * 1.0) * 0.1342;
sum += texture(u_texture, v_uv) * 0.1432;
sum += texture(u_texture, v_uv + offset * 1.0) * 0.1342;
sum += texture(u_texture, v_uv + offset * 2.0) * 0.1110;
sum += texture(u_texture, v_uv + offset * 3.0) * 0.0812;
sum += texture(u_texture, v_uv + offset * 4.0) * 0.0525;
sum += texture(u_texture, v_uv + offset * 5.0) * 0.0300;
sum += texture(u_texture, v_uv + offset * 6.0) * 0.0152;
FragColor = sum;
}
"""
v_frag_src = """
#version 330 core
in vec2 v_uv;
uniform sampler2D u_texture;
uniform vec2 u_texel_size;
out vec4 FragColor;
void main() {
vec2 offset = vec2(0.0, u_texel_size.y);
vec4 sum = vec4(0.0);
sum += texture(u_texture, v_uv - offset * 6.0) * 0.0152;
sum += texture(u_texture, v_uv - offset * 5.0) * 0.0300;
sum += texture(u_texture, v_uv - offset * 4.0) * 0.0525;
sum += texture(u_texture, v_uv - offset * 3.0) * 0.0812;
sum += texture(u_texture, v_uv - offset * 2.0) * 0.1110;
sum += texture(u_texture, v_uv - offset * 1.0) * 0.1342;
sum += texture(u_texture, v_uv) * 0.1432;
sum += texture(u_texture, v_uv + offset * 1.0) * 0.1342;
sum += texture(u_texture, v_uv + offset * 2.0) * 0.1110;
sum += texture(u_texture, v_uv + offset * 3.0) * 0.0812;
sum += texture(u_texture, v_uv + offset * 4.0) * 0.0525;
sum += texture(u_texture, v_uv + offset * 5.0) * 0.0300;
sum += texture(u_texture, v_uv + offset * 6.0) * 0.0152;
FragColor = sum;
}
"""
self.h_blur_program = self._compile_shader(vert_src, h_frag_src)
self.v_blur_program = self._compile_shader(vert_src, v_frag_src)
def compile_deepsea_shader(self):
vert_src = """
#version 330 core
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texcoord;
out vec2 v_uv;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_uv = a_texcoord;
}
"""
frag_src = """
#version 330 core
in vec2 v_uv;
uniform float u_time;
uniform vec2 u_resolution;
out vec4 FragColor;
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}
float fbm(vec2 p) {
float v = 0.0;
float a = 0.5;
for (int i = 0; i < 4; i++) {
v += a * noise(p);
p *= 2.0;
a *= 0.5;
}
return v;
}
void main() {
vec2 uv = v_uv;
float t = u_time * 0.3;
vec3 col = vec3(0.01, 0.05, 0.12);
for (int i = 0; i < 3; i++) {
float phase = t * (0.1 + float(i) * 0.05);
vec2 blob_uv = uv + vec2(sin(phase), cos(phase * 0.8)) * 0.3;
float blob = fbm(blob_uv * 3.0 + t * 0.2);
col = mix(col, vec3(0.02, 0.20, 0.40), blob * 0.4);
}
float line_alpha = 0.0;
for (int i = 0; i < 12; i++) {
float fi = float(i);
float offset = mod(t * 15.0 + fi * (u_resolution.x / 12.0), u_resolution.x);
float line_x = offset / u_resolution.x;
float dist = abs(uv.x - line_x);
float alpha = smoothstep(0.02, 0.0, dist) * (0.1 + 0.05 * sin(t + fi));
line_alpha += alpha;
}
col += vec3(0.04, 0.35, 0.55) * line_alpha;
float vignette = 1.0 - length(uv - 0.5) * 0.8;
col *= vignette;
FragColor = vec4(col, 1.0);
}
"""
self.deepsea_program = self._compile_shader(vert_src, frag_src)
self._quad_vao = self._create_quad_vao()
def render_deepsea_to_fbo(self, width: int, height: int, time: float):
if not self.deepsea_program or not self.scene_fbo or not self._quad_vao:
return
scene_w = width * self._fb_scale
scene_h = height * self._fb_scale
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.scene_fbo)
gl.glViewport(0, 0, scene_w, scene_h)
gl.glClearColor(0.01, 0.05, 0.12, 1.0)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
gl.glUseProgram(self.deepsea_program)
u_time_loc = gl.glGetUniformLocation(self.deepsea_program, "u_time")
if u_time_loc != -1:
gl.glUniform1f(u_time_loc, time)
u_res_loc = gl.glGetUniformLocation(self.deepsea_program, "u_resolution")
if u_res_loc != -1:
gl.glUniform2f(u_res_loc, float(scene_w), float(scene_h))
gl.glBindVertexArray(self._quad_vao)
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
gl.glBindVertexArray(0)
gl.glUseProgram(0)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
def _render_quad(self, program: int, src_tex: int, texel_size: tuple[float, float]):
gl.glUseProgram(program)
gl.glActiveTexture(gl.GL_TEXTURE0)
gl.glBindTexture(gl.GL_TEXTURE_2D, src_tex)
u_tex = gl.glGetUniformLocation(program, "u_texture")
if u_tex != -1:
gl.glUniform1i(u_tex, 0)
u_ts = gl.glGetUniformLocation(program, "u_texel_size")
if u_ts != -1:
gl.glUniform2f(u_ts, texel_size[0], texel_size[1])
gl.glBindVertexArray(self._quad_vao)
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
gl.glBindVertexArray(0)
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
gl.glUseProgram(0)
def prepare_blur(self, width: int, height: int, time: float):
if not self.h_blur_program or not self.v_blur_program:
return
if not self.blur_fbo_a or not self.blur_fbo_b:
return
blur_w = max(1, self._fb_width)
blur_h = max(1, self._fb_height)
texel_x = 1.0 / float(blur_w)
texel_y = 1.0 / float(blur_h)
gl.glViewport(0, 0, blur_w, blur_h)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo_a)
gl.glClearColor(0.0, 0.0, 0.0, 0.0)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
self._render_quad(self.h_blur_program, self.scene_tex, (texel_x, texel_y))
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo_b)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
self._render_quad(self.v_blur_program, self.blur_tex_a, (texel_x, texel_y))
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
restore_w = width * self._fb_scale
restore_h = height * self._fb_scale
gl.glViewport(0, 0, restore_w, restore_h)
def prepare_global_blur(self, width: int, height: int, time: float, fb_scale: float = 1.0):
if not self.scene_fbo:
if self._fb_scale != int(fb_scale):
self.setup_fbos(width, height, fb_scale)
self.render_deepsea_to_fbo(width, height, time)
self.prepare_blur(width, height, time)
def get_blur_texture(self) -> int | None:
return self.blur_tex_b
def cleanup(self):
fbos = [f for f in [self.scene_fbo, self.blur_fbo_a, self.blur_fbo_b] if f is not None]
texs = [t for t in [self.scene_tex, self.blur_tex_a, self.blur_tex_b] if t is not None]
progs = [p for p in [self.h_blur_program, self.v_blur_program, self.deepsea_program] if p is not None]
if fbos:
gl.glDeleteFramebuffers(len(fbos), fbos)
if texs:
gl.glDeleteTextures(len(texs), texs)
if progs:
for p in progs:
gl.glDeleteProgram(p)
if self._quad_vao:
gl.glDeleteVertexArrays(1, [self._quad_vao])
self.scene_fbo = None
self.scene_tex = None
self.blur_fbo_a = None
self.blur_tex_a = None
self.blur_fbo_b = None
self.blur_tex_b = None
self.h_blur_program = None
self.v_blur_program = None
self.deepsea_program = None
self._quad_vao = None