feat(shaders): Implement CRT post-process shader logic

This commit is contained in:
2026-03-13 12:35:43 -04:00
parent 836168a2a8
commit 905ac00e3f
2 changed files with 106 additions and 0 deletions

View File

@@ -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)

View File

@@ -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()