Compare commits
6 Commits
master
...
3d5c768598
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d5c768598 | |||
| f297e7a3bd | |||
| 4a705a8060 | |||
| 55f3bd87e2 | |||
| e56d4af097 | |||
| 1328bc159b |
@@ -1,17 +1,17 @@
|
||||
# Implementation Plan: Frosted Glass Background Effect
|
||||
|
||||
## Phase 1: Shader Development & Integration
|
||||
- [ ] Task: Audit `src/shader_manager.py` to identify existing background/post-process integration points.
|
||||
- [ ] Task: Write Tests: Verify `ShaderManager` can compile and bind a multi-pass blur shader.
|
||||
- [ ] Task: Implement: Add `FrostedGlassShader` (GLSL) to `src/shader_manager.py`.
|
||||
- [ ] Task: Implement: Integrate the blur shader into the `ShaderManager` lifecycle.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Shader Development & Integration' (Protocol in workflow.md)
|
||||
## Phase 1: Shader Development & Integration [checkpoint: 55f3bd8]
|
||||
- [x] Task: Audit `src/shader_manager.py` to identify existing background/post-process integration points. [1328bc1]
|
||||
- [x] Task: Write Tests: Verify `ShaderManager` can compile and bind a multi-pass blur shader. [1328bc1]
|
||||
- [x] Task: Implement: Add `FrostedGlassShader` (GLSL) to `src/shader_manager.py`. [1328bc1]
|
||||
- [x] Task: Implement: Integrate the blur shader into the `ShaderManager` lifecycle. [1328bc1]
|
||||
- [x] Task: Conductor - User Manual Verification 'Phase 1: Shader Development & Integration' (Protocol in workflow.md) [55f3bd8]
|
||||
|
||||
## Phase 2: Framebuffer Capture Pipeline
|
||||
- [ ] Task: Write Tests: Verify the FBO capture mechanism correctly samples the back buffer and stores it in a texture.
|
||||
- [ ] Task: Implement: Update `src/shader_manager.py` or `src/gui_2.py` to handle "pre-rendering" of the background into a texture for blurring.
|
||||
- [ ] Task: Implement: Ensure the blurred texture is updated every frame or on window move events.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Framebuffer Capture Pipeline' (Protocol in workflow.md)
|
||||
- [x] Task: Write Tests: Verify the FBO capture mechanism correctly samples the back buffer and stores it in a texture. [f297e7a]
|
||||
- [x] Task: Implement: Update `src/shader_manager.py` or `src/gui_2.py` to handle "pre-rendering" of the background into a texture for blurring. [f297e7a]
|
||||
- [x] Task: Implement: Ensure the blurred texture is updated every frame or on window move events. [f297e7a]
|
||||
- [~] Task: Conductor - User Manual Verification 'Phase 2: Framebuffer Capture Pipeline' (Protocol in workflow.md)
|
||||
|
||||
## Phase 3: GUI Integration & Rendering
|
||||
- [ ] Task: Write Tests: Verify that a mocked ImGui window successfully calls the frosted glass rendering logic.
|
||||
|
||||
@@ -5,6 +5,39 @@ class ShaderManager:
|
||||
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()
|
||||
@@ -151,3 +184,51 @@ void main() {
|
||||
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)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
import OpenGL.GL as gl
|
||||
|
||||
def test_shader_manager_fbo_initialization():
|
||||
with patch("src.shader_manager.gl") as mock_gl:
|
||||
mock_gl.glGenFramebuffers.return_value = 1
|
||||
mock_gl.glGenTextures.return_value = 2
|
||||
mock_gl.glCheckFramebufferStatus.return_value = mock_gl.GL_FRAMEBUFFER_COMPLETE
|
||||
|
||||
from src.shader_manager import ShaderManager
|
||||
manager = ShaderManager()
|
||||
|
||||
manager.setup_capture_fbo(800, 600)
|
||||
|
||||
assert manager.capture_fbo == 1
|
||||
assert manager.capture_tex == 2
|
||||
assert mock_gl.glGenFramebuffers.called
|
||||
assert mock_gl.glGenTextures.called
|
||||
assert mock_gl.glCheckFramebufferStatus.called
|
||||
|
||||
def test_shader_manager_capture_lifecycle():
|
||||
with patch("src.shader_manager.gl") as mock_gl:
|
||||
mock_gl.glCheckFramebufferStatus.return_value = mock_gl.GL_FRAMEBUFFER_COMPLETE
|
||||
from src.shader_manager import ShaderManager
|
||||
manager = ShaderManager()
|
||||
|
||||
# Ensure setup is called on first capture
|
||||
manager.capture_begin(1024, 768)
|
||||
assert manager.fbo_width == 1024
|
||||
assert manager.fbo_height == 768
|
||||
assert mock_gl.glBindFramebuffer.called
|
||||
|
||||
mock_gl.glBindFramebuffer.reset_mock()
|
||||
manager.capture_end()
|
||||
# Verify unbind (glBindFramebuffer(..., 0))
|
||||
mock_gl.glBindFramebuffer.assert_called_with(mock_gl.GL_FRAMEBUFFER, 0)
|
||||
@@ -0,0 +1,19 @@
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
def test_shader_manager_frosted_glass_compilation():
|
||||
# Mock OpenGL before importing 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()
|
||||
|
||||
# This should fail initially because the method doesn't exist
|
||||
manager.setup_frosted_glass_shader()
|
||||
|
||||
assert manager.blur_program is not None
|
||||
assert mock_gl.glCreateProgram.called
|
||||
Reference in New Issue
Block a user