235 lines
7.1 KiB
Python
235 lines
7.1 KiB
Python
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)
|