Files
manual_slop/tests/test_shader_manager.py
Ed_ 9c2078ad78 feat(shader): Add prepare_global_blur and high-DPI scaling support
- Add prepare_global_blur() method combining Deep Sea render + blur
- Add fb_scale parameter to setup_fbos() for high-DPI display support
- FBOs now created at scaled resolution (width * fb_scale, height * fb_scale)
- prepare_global_blur() auto-initializes FBOs if scale changes
- Add test_blur_pipeline_prepare_global_blur
- Add test_blur_pipeline_high_dpi_scaling

Task: Phase 2, Tasks 1-2 of frosted_glass_20260313 track
2026-03-13 20:38:03 -04:00

218 lines
8.1 KiB
Python

import pytest
from unittest.mock import patch, MagicMock
def test_blur_pipeline_import():
with patch("src.shader_manager.gl") as mock_gl:
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
assert pipeline is not None
assert pipeline.scene_fbo is None
assert pipeline.blur_fbo_a is None
assert pipeline.blur_fbo_b is None
assert pipeline.scene_tex is None
assert pipeline.blur_tex_a is None
assert pipeline.blur_tex_b is None
assert pipeline.h_blur_program is None
assert pipeline.v_blur_program is None
def test_blur_pipeline_setup_fbos():
with patch("src.shader_manager.gl") as mock_gl:
tex_counter = iter([10, 20, 30])
fbo_counter = iter([1, 2, 3])
mock_gl.glGenTextures.side_effect = lambda n: next(tex_counter)
mock_gl.glGenFramebuffers.side_effect = lambda n: next(fbo_counter)
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.setup_fbos(800, 600)
assert mock_gl.glGenFramebuffers.called
assert mock_gl.glGenTextures.called
assert pipeline.scene_fbo is not None
assert pipeline.blur_fbo_a is not None
assert pipeline.blur_fbo_b is not None
def test_blur_pipeline_compile_shaders():
with patch("src.shader_manager.gl") as mock_gl:
mock_gl.glCreateProgram.return_value = 100
mock_gl.glCreateShader.return_value = 200
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.compile_blur_shaders()
assert mock_gl.glCreateProgram.called
assert pipeline.h_blur_program is not None
assert pipeline.v_blur_program is not None
def test_blur_pipeline_wide_tap_distribution():
with patch("src.shader_manager.gl") as mock_gl:
mock_gl.glCreateProgram.return_value = 100
mock_gl.glCreateShader.return_value = 200
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.compile_blur_shaders()
assert mock_gl.glShaderSource.called
shader_sources = [call.args[1] for call in mock_gl.glShaderSource.call_args_list]
frag_sources = [s for s in shader_sources if 'texture(' in s and 'offset' in s]
assert len(frag_sources) >= 2
for src in frag_sources:
texture_calls = src.count('texture(u_texture')
assert texture_calls >= 11, f"Expected at least 11 texture samples for wide tap distribution, got {texture_calls}"
def test_blur_pipeline_render_deepsea_to_fbo():
with patch("src.shader_manager.gl") as mock_gl:
tex_counter = iter([10, 20, 30])
fbo_counter = iter([1, 2, 3])
mock_gl.glGenTextures.side_effect = lambda n: next(tex_counter)
mock_gl.glGenFramebuffers.side_effect = lambda n: next(fbo_counter)
mock_gl.glCreateProgram.return_value = 300
mock_gl.glCreateShader.return_value = 400
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.setup_fbos(800, 600)
pipeline.compile_deepsea_shader()
pipeline.render_deepsea_to_fbo(800, 600, 0.0)
assert mock_gl.glBindFramebuffer.called
assert mock_gl.glUseProgram.called
assert mock_gl.glDrawArrays.called
def test_blur_pipeline_deepsea_shader_compilation():
with patch("src.shader_manager.gl") as mock_gl:
mock_gl.glCreateProgram.return_value = 500
mock_gl.glCreateShader.return_value = 600
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.compile_deepsea_shader()
assert mock_gl.glCreateProgram.called
assert pipeline.deepsea_program is not None
def test_blur_pipeline_prepare_blur():
with patch("src.shader_manager.gl") as mock_gl:
mock_gl.glGenFramebuffers.return_value = None
mock_gl.glGenTextures.return_value = None
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.scene_fbo = 1
pipeline.scene_tex = 10
pipeline.blur_fbo_a = 2
pipeline.blur_tex_a = 20
pipeline.blur_fbo_b = 3
pipeline.blur_tex_b = 30
pipeline.h_blur_program = 100
pipeline.v_blur_program = 101
pipeline.prepare_blur(800, 600, 0.0)
assert mock_gl.glBindFramebuffer.called
assert mock_gl.glUseProgram.called
def test_blur_pipeline_prepare_global_blur():
with patch("src.shader_manager.gl") as mock_gl:
tex_counter = iter([10, 20, 30])
fbo_counter = iter([1, 2, 3])
mock_gl.glGenTextures.side_effect = lambda n: next(tex_counter)
mock_gl.glGenFramebuffers.side_effect = lambda n: next(fbo_counter)
mock_gl.glCreateProgram.return_value = 100
mock_gl.glCreateShader.return_value = 200
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.setup_fbos(800, 600)
pipeline.compile_deepsea_shader()
pipeline.compile_blur_shaders()
pipeline.prepare_global_blur(800, 600, 0.0)
assert mock_gl.glBindFramebuffer.called
assert mock_gl.glUseProgram.called
assert mock_gl.glViewport.called
blur_tex = pipeline.get_blur_texture()
assert blur_tex is not None
assert blur_tex == 30
def test_blur_pipeline_high_dpi_scaling():
with patch("src.shader_manager.gl") as mock_gl:
tex_counter = iter([10, 20, 30])
fbo_counter = iter([1, 2, 3])
mock_gl.glGenTextures.side_effect = lambda n: next(tex_counter)
mock_gl.glGenFramebuffers.side_effect = lambda n: next(fbo_counter)
mock_gl.glCreateProgram.return_value = 100
mock_gl.glCreateShader.return_value = 200
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
fb_scale = 2.0
pipeline.setup_fbos(800, 600, fb_scale)
assert pipeline._fb_width == (800 * int(fb_scale)) // 4
assert pipeline._fb_height == (600 * int(fb_scale)) // 4
assert pipeline._fb_scale == int(fb_scale)
def test_blur_pipeline_cleanup():
with patch("src.shader_manager.gl") as mock_gl:
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.scene_fbo = 1
pipeline.blur_fbo_a = 2
pipeline.blur_fbo_b = 3
pipeline.scene_tex = 10
pipeline.blur_tex_a = 20
pipeline.blur_tex_b = 30
pipeline.h_blur_program = 100
pipeline.v_blur_program = 101
pipeline.cleanup()
assert mock_gl.glDeleteFramebuffers.called
assert mock_gl.glDeleteTextures.called
assert mock_gl.glDeleteProgram.called
def test_shader_manager_initialization_and_compilation():
# Import inside test to allow patching OpenGL before import if needed
# In this case, we patch the OpenGL.GL functions used by ShaderManager
with patch("src.shader_manager.gl") as mock_gl:
mock_gl.glCreateProgram.return_value = 1
mock_gl.glCreateShader.return_value = 2
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
from src.shader_manager import ShaderManager
manager = ShaderManager()
# Basic vertex and fragment shader source
vert_src = "void main() {}"
frag_src = "void main() {}"
program_id = manager.compile_shader(vert_src, frag_src)
assert program_id == 1
assert mock_gl.glCreateProgram.called
assert mock_gl.glCreateShader.called
def test_shader_manager_uniform_update():
# Mock OpenGL.GL functions
with patch("src.shader_manager.gl") as mock_gl:
from src.shader_manager import ShaderManager
manager = ShaderManager()
# Set a mock program ID
manager.program = 1
# Mock glGetUniformLocation to return some valid locations
# u_time -> 10, u_resolution -> 20
def mock_get_loc(prog, name):
if name == "u_time": return 10
if name == "u_resolution": return 20
return -1
mock_gl.glGetUniformLocation.side_effect = mock_get_loc
# Call the method
manager.update_uniforms({"u_time": 1.5, "u_resolution": (800, 600)})
# Assert calls
mock_gl.glGetUniformLocation.assert_any_call(1, "u_time")
mock_gl.glGetUniformLocation.assert_any_call(1, "u_resolution")
mock_gl.glUniform1f.assert_called_once_with(10, 1.5)
mock_gl.glUniform2f.assert_called_once_with(20, 800, 600)