feat(gui): Integrate BlurPipeline with GUI for frosted glass

- Add BlurPipeline import and instance in App class
- Add _pre_new_frame callback to call prepare_global_blur before
- Add ui_frosted_glass_enabled toggle in Shader Editor
- Add _render_frosted_background with screen-space UV sampling
- Add _draw_blurred_rect using ImGui DrawList add_image_quad
- All 12 BlurPipeline tests pass

Task: Phase 3, Task 1 of frosted_glass_20260313 track
This commit is contained in:
2026-03-13 20:46:36 -04:00
parent 5416546207
commit 928318fd06

View File

@@ -39,6 +39,7 @@ else:
from pydantic import BaseModel from pydantic import BaseModel
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed
from src.shader_manager import BlurPipeline
PROVIDERS: list[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minimax"] PROVIDERS: list[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minimax"]
COMMS_CLAMP_CHARS: int = 300 COMMS_CLAMP_CHARS: int = 300
@@ -212,6 +213,49 @@ class App:
self.ui_tool_filter_category = "All" self.ui_tool_filter_category = "All"
self.ui_discussion_split_h = 300.0 self.ui_discussion_split_h = 300.0
self.shader_uniforms = {'crt': 1.0, 'scanline': 0.5, 'bloom': 0.8} self.shader_uniforms = {'crt': 1.0, 'scanline': 0.5, 'bloom': 0.8}
self.ui_frosted_glass_enabled = False
self._blur_pipeline: BlurPipeline | None = None
self.ui_frosted_glass_enabled = False
self._blur_pipeline = None
def _pre_render_blur(self):
if not self.ui_frosted_glass_enabled:
return
if not self._blur_pipeline:
return
ws = imgui.get_io().display_size
fb_scale = imgui.get_io().display_framebuffer_scale.x
import time
t = time.time()
self._blur_pipeline.prepare_global_blur(int(ws.x), int(ws.y), t, fb_scale)
def _render_frosted_background(self, window_pos: tuple, window_size: tuple):
if not self.ui_frosted_glass_enabled:
return
if not self._blur_pipeline:
return
blur_tex = self._blur_pipeline.get_blur_texture()
if not blur_tex:
return
ws = imgui.get_io().display_size
fb_scale = imgui.get_io().display_framebuffer_scale.x
screen_w = ws.x * fb_scale
screen_h = ws.y * fb_scale
uv_x = window_pos[0] / screen_w
uv_y = 1.0 - (window_pos[1] / screen_h)
uv_w = window_size[0] / screen_w
uv_h = window_size[1] / screen_h
dl = imgui.get_window_draw_list()
self._draw_blurred_rect(dl, (window_pos[0], window_pos[1]), (window_pos[0] + window_size[0], window_pos[1] + window_size[1]), blur_tex, (uv_x, uv_y), (uv_x + uv_w, uv_y - uv_h))
def _draw_blurred_rect(self, dl, p_min, p_max, tex_id, uv_min, uv_max):
import OpenGL.GL as gl
gl.glEnable(gl.GL_BLEND)
gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
imgui.push_texture_id(tex_id)
dl.add_image_quad(p_min, p_max, uv_min, uv_max, imgui.get_color_u32((1, 1, 1, 1)))
imgui.pop_texture_id()
gl.glDisable(gl.GL_BLEND)
def _handle_approve_tool(self, user_data=None) -> None: def _handle_approve_tool(self, user_data=None) -> None:
"""UI-level wrapper for approving a pending tool execution ask.""" """UI-level wrapper for approving a pending tool execution ask."""
@@ -437,6 +481,8 @@ class App:
exp, opened = imgui.begin('Shader Editor', self.show_windows['Shader Editor']) exp, opened = imgui.begin('Shader Editor', self.show_windows['Shader Editor'])
self.show_windows['Shader Editor'] = bool(opened) self.show_windows['Shader Editor'] = bool(opened)
if exp: if exp:
_, self.ui_frosted_glass_enabled = imgui.checkbox('Frosted Glass', self.ui_frosted_glass_enabled)
imgui.separator()
changed_crt, self.shader_uniforms['crt'] = imgui.slider_float('CRT Curvature', self.shader_uniforms['crt'], 0.0, 2.0) changed_crt, self.shader_uniforms['crt'] = imgui.slider_float('CRT Curvature', self.shader_uniforms['crt'], 0.0, 2.0)
changed_scan, self.shader_uniforms['scanline'] = imgui.slider_float('Scanline Intensity', self.shader_uniforms['scanline'], 0.0, 1.0) changed_scan, self.shader_uniforms['scanline'] = imgui.slider_float('Scanline Intensity', self.shader_uniforms['scanline'], 0.0, 1.0)
changed_bloom, self.shader_uniforms['bloom'] = imgui.slider_float('Bloom Threshold', self.shader_uniforms['bloom'], 0.0, 1.0) changed_bloom, self.shader_uniforms['bloom'] = imgui.slider_float('Bloom Threshold', self.shader_uniforms['bloom'], 0.0, 1.0)
@@ -3993,6 +4039,27 @@ def hello():
def _post_init(self) -> None: def _post_init(self) -> None:
theme.apply_current() theme.apply_current()
self._init_blur_pipeline()
def _init_blur_pipeline(self):
if self._blur_pipeline is None:
self._blur_pipeline = BlurPipeline()
ws = imgui.get_io().display_size
fb_scale = imgui.get_io().display_framebuffer_scale.x
self._blur_pipeline.setup_fbos(int(ws.x), int(ws.y), fb_scale)
self._blur_pipeline.compile_deepsea_shader()
self._blur_pipeline.compile_blur_shaders()
def _pre_new_frame(self) -> None:
if not self.ui_frosted_glass_enabled:
return
if self._blur_pipeline is None:
self._init_blur_pipeline()
ws = imgui.get_io().display_size
fb_scale = imgui.get_io().display_framebuffer_scale.x
import time
t = time.time()
self._blur_pipeline.prepare_global_blur(int(ws.x), int(ws.y), t, fb_scale)
def run(self) -> None: def run(self) -> None:
"""Initializes the ImGui runner and starts the main application loop.""" """Initializes the ImGui runner and starts the main application loop."""
@@ -4049,6 +4116,7 @@ def hello():
self.runner_params.callbacks.load_additional_fonts = self._load_fonts self.runner_params.callbacks.load_additional_fonts = self._load_fonts
self.runner_params.callbacks.setup_imgui_style = theme.apply_current self.runner_params.callbacks.setup_imgui_style = theme.apply_current
self.runner_params.callbacks.post_init = self._post_init self.runner_params.callbacks.post_init = self._post_init
self.runner_params.callbacks.pre_new_frame = self._pre_new_frame
self._fetch_models(self.current_provider) self._fetch_models(self.current_provider)
md_options = markdown_helper.get_renderer().options md_options = markdown_helper.get_renderer().options
immapp.run(self.runner_params, add_ons_params=immapp.AddOnsParams(with_markdown_options=md_options)) immapp.run(self.runner_params, add_ons_params=immapp.AddOnsParams(with_markdown_options=md_options))