import OpenGL.GL as gl class ShaderManager: def __init__(self): self.program = None self.bg_program = None self.pp_program = None self.blur_program = None self.capture_fbo = None self.capture_tex = None self.fbo_width = 0 self.fbo_height = 0 def setup_capture_fbo(self, width, height): if self.capture_fbo is not None: gl.glDeleteFramebuffers(1, [self.capture_fbo]) if self.capture_tex is not None: gl.glDeleteTextures(1, [self.capture_tex]) self.capture_fbo = gl.glGenFramebuffers(1) self.capture_tex = gl.glGenTextures(1) gl.glBindTexture(gl.GL_TEXTURE_2D, self.capture_tex) gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, 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.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.capture_fbo) gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, self.capture_tex, 0) if gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) != gl.GL_FRAMEBUFFER_COMPLETE: raise RuntimeError("Framebuffer not complete") gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0) self.fbo_width = width self.fbo_height = height def capture_begin(self, width, height): if self.capture_fbo is None or self.fbo_width != width or self.fbo_height != height: self.setup_capture_fbo(width, height) gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.capture_fbo) gl.glViewport(0, 0, width, height) def capture_end(self): gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0) 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) gl.glUseProgram(0) def setup_frosted_glass_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_blur_radius; uniform float u_tint_intensity; uniform float u_opacity; out vec4 FragColor; void main() { vec2 res = vec2(textureSize(u_texture, 0)); vec2 offset = u_blur_radius / res; vec4 sum = vec4(0.0); sum += texture(u_texture, v_uv + vec2(-offset.x, -offset.y)) * 0.0625; sum += texture(u_texture, v_uv + vec2(0.0, -offset.y)) * 0.125; sum += texture(u_texture, v_uv + vec2(offset.x, -offset.y)) * 0.0625; sum += texture(u_texture, v_uv + vec2(-offset.x, 0.0)) * 0.125; sum += texture(u_texture, v_uv + vec2(0.0, 0.0)) * 0.25; sum += texture(u_texture, v_uv + vec2(offset.x, 0.0)) * 0.125; sum += texture(u_texture, v_uv + vec2(-offset.x, offset.y)) * 0.0625; sum += texture(u_texture, v_uv + vec2(0.0, offset.y)) * 0.125; sum += texture(u_texture, v_uv + vec2(offset.x, offset.y)) * 0.0625; vec3 tinted = mix(sum.rgb, vec3(1.0), u_tint_intensity); FragColor = vec4(tinted, sum.a * u_opacity); } """ self.blur_program = self.compile_shader(vertex_src, fragment_src)