diff --git a/src/shader_manager.py b/src/shader_manager.py index c403aa5..da535d9 100644 --- a/src/shader_manager.py +++ b/src/shader_manager.py @@ -4,6 +4,7 @@ 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() @@ -98,3 +99,55 @@ void main() { 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) diff --git a/tests/test_post_process.py b/tests/test_post_process.py new file mode 100644 index 0000000..16703a2 --- /dev/null +++ b/tests/test_post_process.py @@ -0,0 +1,53 @@ +import unittest +from unittest.mock import MagicMock, patch +import sys + +# Mock OpenGL.GL before importing ShaderManager +gl_mock = MagicMock() +# Setup some constants +gl_mock.GL_VERTEX_SHADER = 0x8B31 +gl_mock.GL_FRAGMENT_SHADER = 0x8B30 +gl_mock.GL_COMPILE_STATUS = 0x8B81 +gl_mock.GL_LINK_STATUS = 0x8B82 +gl_mock.GL_TEXTURE0 = 0x84C0 +gl_mock.GL_TEXTURE_2D = 0x0DE1 +gl_mock.GL_TRIANGLE_STRIP = 0x0005 + +opengl_mock = MagicMock() +sys.modules['OpenGL'] = opengl_mock +sys.modules['OpenGL.GL'] = gl_mock +opengl_mock.GL = gl_mock + +from src.shader_manager import ShaderManager + +class TestPostProcess(unittest.TestCase): + def setUp(self): + gl_mock.reset_mock() + # Mock return values for shader compilation + gl_mock.glCreateProgram.return_value = 1 + gl_mock.glCreateShader.return_value = 2 + gl_mock.glGetShaderiv.return_value = 1 # GL_TRUE + gl_mock.glGetProgramiv.return_value = 1 # GL_TRUE + gl_mock.glGetUniformLocation.return_value = 10 + + def test_setup_post_process_shader(self): + sm = ShaderManager() + sm.setup_post_process_shader() + self.assertEqual(sm.pp_program, 1) + gl_mock.glCreateProgram.assert_called() + gl_mock.glLinkProgram.assert_called_with(1) + + def test_render_post_process(self): + sm = ShaderManager() + sm.pp_program = 1 + sm.render_post_process(texture_id=5, width=800, height=600, time=1.0) + + gl_mock.glUseProgram.assert_any_call(1) + gl_mock.glActiveTexture.assert_called_with(gl_mock.GL_TEXTURE0) + gl_mock.glBindTexture.assert_any_call(gl_mock.GL_TEXTURE_2D, 5) + gl_mock.glUniform1f.assert_called() + gl_mock.glDrawArrays.assert_called_with(gl_mock.GL_TRIANGLE_STRIP, 0, 4) + gl_mock.glUseProgram.assert_any_call(0) + +if __name__ == '__main__': + unittest.main()