diff --git a/src/gui_2.py b/src/gui_2.py index c980d50..5447b2e 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -223,6 +223,7 @@ class App: } self.shader_manager = ShaderManager() + self.start_time = time.time() def _handle_approve_tool(self, user_data=None) -> None: """UI-level wrapper for approving a pending tool execution ask.""" @@ -355,21 +356,60 @@ class App: def _render_frosted_background(self, pos: imgui.ImVec2, size: imgui.ImVec2) -> None: if not theme.get_frosted_glass_enabled(): return if size.x <= 0 or size.y <= 0: return - display_size = imgui.get_io().display_size + try: + ws = immapp.get_window_size() + display_size = imgui.ImVec2(float(ws[0]), float(ws[1])) + except Exception: + display_size = imgui.get_io().display_size + + if display_size.x <= 0 or display_size.y <= 0: return + uv_min = imgui.ImVec2(pos.x / display_size.x, 1.0 - pos.y / display_size.y) uv_max = imgui.ImVec2((pos.x + size.x) / display_size.x, 1.0 - (pos.y + size.y) / display_size.y) - imgui.get_window_draw_list().add_image( - imgui.ImTextureRef(self.shader_manager.blur_tex), + + if self.shader_manager.blur_tex: + imgui.get_window_draw_list().add_image( + imgui.ImTextureRef(self.shader_manager.blur_tex), + pos, + imgui.ImVec2(pos.x + size.x, pos.y + size.y), + uv_min, + uv_max + ) + else: + # Fallback: semi-transparent rectangle to help debug visibility + imgui.get_window_draw_list().add_rect_filled( + pos, + imgui.ImVec2(pos.x + size.x, pos.y + size.y), + imgui.get_color_u32(vec4(30, 30, 40, 0.7)) + ) + + # Increase contrast with a 0.2 alpha tint overlay + imgui.get_window_draw_list().add_rect_filled( pos, imgui.ImVec2(pos.x + size.x, pos.y + size.y), - uv_min, - uv_max + imgui.get_color_u32(vec4(0, 0, 0, 0.2)) ) + + def _begin_window(self, name: str, p_open: Any = None, flags: int = 0) -> tuple[bool, Any]: + frosted = theme.get_frosted_glass_enabled() + if frosted: + imgui.push_style_color(imgui.Col_.window_bg, vec4(0, 0, 0, 0)) + + expanded, opened = imgui.begin(name, p_open, flags) + + if expanded and frosted: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) + + return expanded, opened + + def _end_window(self) -> None: + imgui.end() + if theme.get_frosted_glass_enabled(): + imgui.pop_style_color() def _render_operations_hub(self) -> None: - exp, opened = imgui.begin("Operations Hub", self.show_windows["Operations Hub"]) + exp, opened = self._begin_window("Operations Hub", self.show_windows["Operations Hub"]) self.show_windows["Operations Hub"] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) imgui.text("Focus Agent:") imgui.same_line() focus_label = self.ui_focus_agent or "All" @@ -416,7 +456,7 @@ class App: self._render_external_tools_panel() imgui.end_tab_item() imgui.end_tab_bar() - imgui.end() + self._end_window() def _show_menus(self) -> None: if imgui.begin_menu("manual slop"): @@ -520,7 +560,7 @@ class App: imgui.separator() imgui.text("Frosted Glass") - changed_fbr, self.shader_uniforms['frosted_blur_radius'] = imgui.slider_float('Blur Radius', self.shader_uniforms['frosted_blur_radius'], 0.0, 32.0) + changed_fbr, self.shader_uniforms['frosted_blur_radius'] = imgui.slider_float('Blur Radius', self.shader_uniforms['frosted_blur_radius'], 0.0, 64.0) if changed_fbr: theme.set_frosted_blur_radius(self.shader_uniforms['frosted_blur_radius']) changed_fti, self.shader_uniforms['frosted_tint_intensity'] = imgui.slider_float('Tint Intensity', self.shader_uniforms['frosted_tint_intensity'], 0.0, 1.0) @@ -534,12 +574,31 @@ class App: self._render_custom_title_bar() self._render_shader_live_editor() pushed_prior_tint = False + pushed_frosted_style = False + # Render background shader bg = bg_shader.get_bg() - if bg.enabled: + frosted_enabled = theme.get_frosted_glass_enabled() + if bg.enabled and not frosted_enabled: ws = imgui.get_io().display_size bg.render(ws.x, ws.y) + if frosted_enabled: + ws = imgui.get_io().display_size + self.shader_manager.prepare_global_blur( + int(ws.x), int(ws.y), + self.shader_uniforms['frosted_blur_radius'], + self.shader_uniforms['frosted_tint_intensity'], + self.shader_uniforms['frosted_opacity'], + time.time() + ) + # Draw background texture + dl = imgui.get_background_draw_list() + dl.add_image(imgui.ImTextureRef(self.shader_manager.scene_tex), (0, 0), (ws.x, ws.y)) + + imgui.push_style_color(imgui.Col_.window_bg, vec4(0, 0, 0, 0)) + pushed_frosted_style = True + if theme.is_nerv_active(): ws = imgui.get_io().display_size self._nerv_alert.update(self.ai_status) @@ -547,14 +606,6 @@ class App: self._nerv_crt.enabled = self.ui_crt_filter self._nerv_crt.render(ws.x, ws.y) - if theme.get_frosted_glass_enabled(): - ws = imgui.get_io().display_size - self.shader_manager.prepare_global_blur( - int(ws.x), int(ws.y), - self.shader_uniforms['frosted_blur_radius'], - self.shader_uniforms['frosted_tint_intensity'], - self.shader_uniforms['frosted_opacity'] - ) if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func") if self.is_viewing_prior_session: imgui.push_style_color(imgui.Col_.window_bg, vec4(50, 40, 20)) @@ -627,10 +678,9 @@ class App: self._tool_log_dirty = False if self.show_windows.get("Context Hub", False): - exp, opened = imgui.begin("Context Hub", self.show_windows["Context Hub"]) + exp, opened = self._begin_window("Context Hub", self.show_windows["Context Hub"]) self.show_windows["Context Hub"] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) if imgui.begin_tab_bar('context_hub_tabs'): if imgui.begin_tab_item('Projects')[0]: self._render_projects_panel() @@ -639,22 +689,20 @@ class App: self._render_paths_panel() imgui.end_tab_item() imgui.end_tab_bar() - imgui.end() + self._end_window() if self.show_windows.get("Files & Media", False): - exp, opened = imgui.begin("Files & Media", self.show_windows["Files & Media"]) + exp, opened = self._begin_window("Files & Media", self.show_windows["Files & Media"]) self.show_windows["Files & Media"] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) if imgui.collapsing_header("Files"): self._render_files_panel() if imgui.collapsing_header("Screenshots"): self._render_screenshots_panel() - imgui.end() + self._end_window() if self.show_windows.get("AI Settings", False): - exp, opened = imgui.begin("AI Settings", self.show_windows["AI Settings"]) + exp, opened = self._begin_window("AI Settings", self.show_windows["AI Settings"]) self.show_windows["AI Settings"] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_persona_selector_panel() if imgui.collapsing_header("Provider & Model"): self._render_provider_panel() @@ -662,66 +710,58 @@ class App: self._render_system_prompts_panel() self._render_agent_tools_panel() - imgui.end() + self._end_window() if self.ui_separate_usage_analytics and self.show_windows.get("Usage Analytics", False): - exp, opened = imgui.begin("Usage Analytics", self.show_windows["Usage Analytics"]) + exp, opened = self._begin_window("Usage Analytics", self.show_windows["Usage Analytics"]) self.show_windows["Usage Analytics"] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_usage_analytics_panel() - imgui.end() + self._end_window() if self.show_windows.get("MMA Dashboard", False): - exp, opened = imgui.begin("MMA Dashboard", self.show_windows["MMA Dashboard"]) + exp, opened = self._begin_window("MMA Dashboard", self.show_windows["MMA Dashboard"]) self.show_windows["MMA Dashboard"] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard") self._render_mma_dashboard() if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard") - imgui.end() + self._end_window() if self.ui_separate_task_dag and self.show_windows.get("Task DAG", False): - exp, opened = imgui.begin("Task DAG", self.show_windows["Task DAG"]) + exp, opened = self._begin_window("Task DAG", self.show_windows["Task DAG"]) self.show_windows["Task DAG"] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_task_dag_panel() - imgui.end() + self._end_window() if self.ui_separate_tier1 and self.show_windows.get("Tier 1: Strategy", False): - exp, opened = imgui.begin("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"]) + exp, opened = self._begin_window("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"]) self.show_windows["Tier 1: Strategy"] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_tier_stream_panel("Tier 1", "Tier 1") - imgui.end() + self._end_window() if self.ui_separate_tier2 and self.show_windows.get("Tier 2: Tech Lead", False): - exp, opened = imgui.begin("Tier 2: Tech Lead", self.show_windows["Tier 2: Tech Lead"]) + exp, opened = self._begin_window("Tier 2: Tech Lead", self.show_windows["Tier 2: Tech Lead"]) self.show_windows["Tier 2: Tech Lead"] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_tier_stream_panel("Tier 2", "Tier 2 (Tech Lead)") - imgui.end() + self._end_window() if self.ui_separate_tier3 and self.show_windows.get("Tier 3: Workers", False): - exp, opened = imgui.begin("Tier 3: Workers", self.show_windows["Tier 3: Workers"]) + exp, opened = self._begin_window("Tier 3: Workers", self.show_windows["Tier 3: Workers"]) self.show_windows["Tier 3: Workers"] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_tier_stream_panel("Tier 3", None) - imgui.end() + self._end_window() if self.ui_separate_tier4 and self.show_windows.get("Tier 4: QA", False): - exp, opened = imgui.begin("Tier 4: QA", self.show_windows["Tier 4: QA"]) + exp, opened = self._begin_window("Tier 4: QA", self.show_windows["Tier 4: QA"]) self.show_windows["Tier 4: QA"] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)") - imgui.end() + self._end_window() if self.show_windows.get("Theme", False): self._render_theme_panel() if self.show_windows.get("Discussion Hub", False): - exp, opened = imgui.begin("Discussion Hub", self.show_windows["Discussion Hub"]) + exp, opened = self._begin_window("Discussion Hub", self.show_windows["Discussion Hub"]) self.show_windows["Discussion Hub"] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) # Top part for the history imgui.begin_child("HistoryChild", size=(0, -self.ui_discussion_split_h)) if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel") @@ -766,41 +806,37 @@ class App: else: imgui.text_disabled("Message & Response panels are detached.") - imgui.end() + self._end_window() if self.show_windows.get("Operations Hub", False): self._render_operations_hub() if self.ui_separate_message_panel and self.show_windows.get("Message", False): - exp, opened = imgui.begin("Message", self.show_windows["Message"]) + exp, opened = self._begin_window("Message", self.show_windows["Message"]) self.show_windows["Message"] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_message_panel() - imgui.end() + self._end_window() if self.ui_separate_response_panel and self.show_windows.get("Response", False): - exp, opened = imgui.begin("Response", self.show_windows["Response"]) + exp, opened = self._begin_window("Response", self.show_windows["Response"]) self.show_windows["Response"] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_response_panel() - imgui.end() + self._end_window() if self.ui_separate_tool_calls_panel and self.show_windows.get("Tool Calls", False): - exp, opened = imgui.begin("Tool Calls", self.show_windows["Tool Calls"]) + exp, opened = self._begin_window("Tool Calls", self.show_windows["Tool Calls"]) self.show_windows["Tool Calls"] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_tool_calls_panel() - imgui.end() + self._end_window() if self.ui_separate_external_tools and self.show_windows.get('External Tools', False): - exp, opened = imgui.begin('External Tools', self.show_windows['External Tools']) + exp, opened = self._begin_window('External Tools', self.show_windows['External Tools']) self.show_windows['External Tools'] = bool(opened) if exp: - self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_external_tools_panel() - imgui.end() + self._end_window() if self.show_windows.get("Log Management", False): if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_log_management") @@ -1083,6 +1119,8 @@ class App: if pushed_prior_tint: imgui.pop_style_color() + if pushed_frosted_style: + imgui.pop_style_color() if self.perf_profiling_enabled: self.perf_monitor.end_component("_gui_func") diff --git a/src/shader_manager.py b/src/shader_manager.py index 2429252..30f67a8 100644 --- a/src/shader_manager.py +++ b/src/shader_manager.py @@ -5,10 +5,14 @@ class ShaderManager: self.program = None self.bg_program = None self.pp_program = None - self.blur_program = None + self.blur_h_program = None + self.blur_v_program = None self.blur_fbo = None + self.scene_fbo = None + self.temp_fbo = None self.scene_tex = None self.blur_tex = None + self.temp_tex = None self.fbo_width = 0 self.fbo_height = 0 self._vao = None @@ -16,7 +20,12 @@ class ShaderManager: def _ensure_vao(self): if self._vao is None: try: - self._vao = gl.glGenVertexArrays(1) + import sys + if sys.platform == "win32": + self._vao = gl.glGenVertexArrays(1) + else: + # Some non-win32 environments might not support VAOs or need different handling + self._vao = gl.glGenVertexArrays(1) except Exception: pass if self._vao is not None: @@ -25,36 +34,73 @@ class ShaderManager: def setup_capture_fbo(self, width, height): if self.blur_fbo is not None: gl.glDeleteFramebuffers(1, [self.blur_fbo]) + if self.scene_fbo is not None: + gl.glDeleteFramebuffers(1, [self.scene_fbo]) + if self.temp_fbo is not None: + gl.glDeleteFramebuffers(1, [self.temp_fbo]) if self.scene_tex is not None: gl.glDeleteTextures(1, [self.scene_tex]) if self.blur_tex is not None: gl.glDeleteTextures(1, [self.blur_tex]) - self.blur_fbo = gl.glGenFramebuffers(1) + if self.temp_tex is not None: + gl.glDeleteTextures(1, [self.temp_tex]) + self.scene_tex = gl.glGenTextures(1) gl.glBindTexture(gl.GL_TEXTURE_2D, self.scene_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.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE) + gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE) + + self.scene_fbo = gl.glGenFramebuffers(1) + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.scene_fbo) + gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, self.scene_tex, 0) + if gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) != gl.GL_FRAMEBUFFER_COMPLETE: + raise RuntimeError("Scene Framebuffer not complete") + + self.temp_tex = gl.glGenTextures(1) + gl.glBindTexture(gl.GL_TEXTURE_2D, self.temp_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.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE) + gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE) + + self.temp_fbo = gl.glGenFramebuffers(1) + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.temp_fbo) + gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, self.temp_tex, 0) + if gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) != gl.GL_FRAMEBUFFER_COMPLETE: + raise RuntimeError("Temp Framebuffer not complete") + self.blur_tex = gl.glGenTextures(1) gl.glBindTexture(gl.GL_TEXTURE_2D, self.blur_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.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE) + gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE) + + self.blur_fbo = gl.glGenFramebuffers(1) gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo) gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, self.blur_tex, 0) if gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) != gl.GL_FRAMEBUFFER_COMPLETE: - raise RuntimeError("Framebuffer not complete") + raise RuntimeError("Blur Framebuffer not complete") + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0) self.fbo_width = width self.fbo_height = height - def prepare_global_blur(self, width, height, radius, tint, opacity): - if self.blur_fbo is None or self.fbo_width != width or self.fbo_height != height: + def render_background_to_fbo(self, width, height, time): + if self.scene_fbo is None or self.fbo_width != width or self.fbo_height != height: self.setup_capture_fbo(width, height) - gl.glBindTexture(gl.GL_TEXTURE_2D, self.scene_tex) - gl.glCopyTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, 0, 0, width, height, 0) - gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo) + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.scene_fbo) gl.glViewport(0, 0, width, height) + self.render_background(width, height, time) + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0) + + def prepare_global_blur(self, width, height, radius, tint, opacity, time): + self.render_background_to_fbo(width, height, time) self.render_blur(self.scene_tex, width, height, radius, tint, opacity) gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0) @@ -140,9 +186,44 @@ void main() { uniform float u_time; uniform vec2 u_resolution; out vec4 FragColor; + +float hash(vec2 p) { + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); +} + +float noise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + vec2 u = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x), + mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), u.y); +} + void main() { vec2 uv = gl_FragCoord.xy / u_resolution.xy; - vec3 col = 0.5 + 0.5 * cos(u_time + uv.xyx + vec3(0, 2, 4)); + vec2 p = uv * 2.0 - 1.0; + p.x *= u_resolution.x / u_resolution.y; + + // Deep sea background gradient (dark blue) + vec3 col = mix(vec3(0.01, 0.03, 0.08), vec3(0.0, 0.08, 0.15), uv.y); + + // Moving blobs / caustics + float n = 0.0; + float t = u_time * 0.15; + n += noise(p * 1.2 + vec2(t * 0.8, t * 0.5)) * 0.4; + n += noise(p * 2.5 - vec2(t * 0.4, t * 0.9)) * 0.2; + + col += vec3(0.05, 0.12, 0.22) * n; + + // Bright highlights (caustics approximation) + float c = 0.0; + for(int i=0; i<3; i++) { + vec2 p2 = p * (float(i) + 1.0) * 0.4; + p2 += vec2(sin(t + p2.y * 1.5), cos(t + p2.x * 1.5)); + c += abs(0.015 / (length(p2) - 0.4)); + } + col += vec3(0.1, 0.25, 0.45) * c * 0.12; + FragColor = vec4(col, 1.0); } """ @@ -236,56 +317,88 @@ void main() { v_uv = uvs[gl_VertexID]; } """ - fragment_src = """ + fragment_src_h = """ #version 330 core in vec2 v_uv; uniform sampler2D u_texture; uniform float u_blur_radius; +uniform vec2 u_direction; +out vec4 FragColor; +void main() { + float weight[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); + vec2 res = vec2(textureSize(u_texture, 0)); + vec2 tex_offset = (u_blur_radius / res) * u_direction * 2.5; // Multiplied by 2.5 for milky effect + vec4 result = texture(u_texture, v_uv) * weight[0]; + for(int i = 1; i < 5; ++i) { + result += texture(u_texture, v_uv + tex_offset * float(i)) * weight[i]; + result += texture(u_texture, v_uv - tex_offset * float(i)) * weight[i]; + } + FragColor = result; +} +""" + fragment_src_v = """ +#version 330 core +in vec2 v_uv; +uniform sampler2D u_texture; +uniform float u_blur_radius; +uniform vec2 u_direction; uniform float u_tint_intensity; uniform float u_opacity; out vec4 FragColor; void main() { + float weight[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); vec2 res = vec2(textureSize(u_texture, 0)); - vec2 offset = u_blur_radius / res; - vec4 sum = vec4(0.0); - sum += texture(u_texture, v_uv) * 0.1839; - sum += texture(u_texture, v_uv + vec2(offset.x, 0.0)) * 0.1114; - sum += texture(u_texture, v_uv + vec2(-offset.x, 0.0)) * 0.1114; - sum += texture(u_texture, v_uv + vec2(0.0, offset.y)) * 0.1114; - sum += texture(u_texture, v_uv + vec2(0.0, -offset.y)) * 0.1114; - sum += texture(u_texture, v_uv + vec2(offset.x, offset.y)) * 0.0677; - sum += texture(u_texture, v_uv + vec2(-offset.x, offset.y)) * 0.0677; - sum += texture(u_texture, v_uv + vec2(offset.x, -offset.y)) * 0.0677; - sum += texture(u_texture, v_uv + vec2(-offset.x, -offset.y)) * 0.0677; - sum += texture(u_texture, v_uv + vec2(offset.x * 2.0, 0.0)) * 0.0248; - sum += texture(u_texture, v_uv + vec2(-offset.x * 2.0, 0.0)) * 0.0248; - sum += texture(u_texture, v_uv + vec2(0.0, offset.y * 2.0)) * 0.0248; - sum += texture(u_texture, v_uv + vec2(0.0, -offset.y * 2.0)) * 0.0248; - vec3 tinted = mix(sum.rgb, vec3(1.0), u_tint_intensity); - FragColor = vec4(tinted, sum.a * u_opacity); + vec2 tex_offset = (u_blur_radius / res) * u_direction * 2.5; // Multiplied by 2.5 for milky effect + vec4 result = texture(u_texture, v_uv) * weight[0]; + for(int i = 1; i < 5; ++i) { + result += texture(u_texture, v_uv + tex_offset * float(i)) * weight[i]; + result += texture(u_texture, v_uv - tex_offset * float(i)) * weight[i]; + } + vec3 tint_color = vec3(0.05, 0.07, 0.12); // Slightly deeper tint + vec3 tinted = mix(result.rgb, tint_color, u_tint_intensity); + FragColor = vec4(tinted, result.a * u_opacity); } """ - self.blur_program = self.compile_shader(vertex_src, fragment_src) + self.blur_h_program = self.compile_shader(vertex_src, fragment_src_h) + self.blur_v_program = self.compile_shader(vertex_src, fragment_src_v) def render_blur(self, texture_id, width, height, radius, tint, opacity): - if not self.blur_program: + if not self.blur_h_program or not self.blur_v_program: return - gl.glUseProgram(self.blur_program) + + self._ensure_vao() + + # Pass 1: Horizontal blur to temp_fbo + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.temp_fbo) + gl.glViewport(0, 0, width, height) + gl.glClear(gl.GL_COLOR_BUFFER_BIT) + + gl.glUseProgram(self.blur_h_program) gl.glActiveTexture(gl.GL_TEXTURE0) gl.glBindTexture(gl.GL_TEXTURE_2D, texture_id) - u_tex_loc = gl.glGetUniformLocation(self.blur_program, "u_texture") - if u_tex_loc != -1: - gl.glUniform1i(u_tex_loc, 0) - u_radius_loc = gl.glGetUniformLocation(self.blur_program, "u_blur_radius") - if u_radius_loc != -1: - gl.glUniform1f(u_radius_loc, float(radius)) - u_tint_loc = gl.glGetUniformLocation(self.blur_program, "u_tint_intensity") - if u_tint_loc != -1: - gl.glUniform1f(u_tint_loc, float(tint)) - u_opacity_loc = gl.glGetUniformLocation(self.blur_program, "u_opacity") - if u_opacity_loc != -1: - gl.glUniform1f(u_opacity_loc, float(opacity)) - self._ensure_vao() + + gl.glUniform1i(gl.glGetUniformLocation(self.blur_h_program, "u_texture"), 0) + gl.glUniform1f(gl.glGetUniformLocation(self.blur_h_program, "u_blur_radius"), float(radius)) + gl.glUniform2f(gl.glGetUniformLocation(self.blur_h_program, "u_direction"), 1.0, 0.0) + gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4) + + # Pass 2: Vertical blur to blur_fbo + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo) + gl.glViewport(0, 0, width, height) + gl.glClear(gl.GL_COLOR_BUFFER_BIT) + + gl.glUseProgram(self.blur_v_program) + gl.glActiveTexture(gl.GL_TEXTURE0) + gl.glBindTexture(gl.GL_TEXTURE_2D, self.temp_tex) + + gl.glUniform1i(gl.glGetUniformLocation(self.blur_v_program, "u_texture"), 0) + gl.glUniform1f(gl.glGetUniformLocation(self.blur_v_program, "u_blur_radius"), float(radius)) + gl.glUniform2f(gl.glGetUniformLocation(self.blur_v_program, "u_direction"), 0.0, 1.0) + gl.glUniform1f(gl.glGetUniformLocation(self.blur_v_program, "u_tint_intensity"), float(tint)) + gl.glUniform1f(gl.glGetUniformLocation(self.blur_v_program, "u_opacity"), float(opacity)) + + gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4) + gl.glBindTexture(gl.GL_TEXTURE_2D, 0) gl.glUseProgram(0) diff --git a/tests/test_fbo_capture.py b/tests/test_fbo_capture.py index 129840a..2798031 100644 --- a/tests/test_fbo_capture.py +++ b/tests/test_fbo_capture.py @@ -4,8 +4,8 @@ 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.glGenFramebuffers.side_effect = [1, 2] + mock_gl.glGenTextures.side_effect = [3, 4] mock_gl.glCheckFramebufferStatus.return_value = mock_gl.GL_FRAMEBUFFER_COMPLETE from src.shader_manager import ShaderManager @@ -13,15 +13,19 @@ def test_shader_manager_fbo_initialization(): 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 + assert manager.scene_fbo == 1 + assert manager.blur_fbo == 2 + assert manager.scene_tex == 3 + assert manager.blur_tex == 4 + assert mock_gl.glGenFramebuffers.call_count == 2 + assert mock_gl.glGenTextures.call_count == 2 + assert mock_gl.glCheckFramebufferStatus.call_count == 2 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 + mock_gl.glGenFramebuffers.side_effect = [1, 2] + mock_gl.glGenTextures.side_effect = [3, 4] from src.shader_manager import ShaderManager manager = ShaderManager() @@ -29,7 +33,8 @@ def test_shader_manager_capture_lifecycle(): manager.capture_begin(1024, 768) assert manager.fbo_width == 1024 assert manager.fbo_height == 768 - assert mock_gl.glBindFramebuffer.called + # Should bind the blur FBO + mock_gl.glBindFramebuffer.assert_any_call(mock_gl.GL_FRAMEBUFFER, manager.blur_fbo) mock_gl.glBindFramebuffer.reset_mock() manager.capture_end() diff --git a/tests/test_gui_frosted_integration.py b/tests/test_gui_frosted_integration.py index e4520da..b5fc8a1 100644 --- a/tests/test_gui_frosted_integration.py +++ b/tests/test_gui_frosted_integration.py @@ -1,28 +1,89 @@ import pytest +import time from unittest.mock import patch, MagicMock def test_gui_frosted_background_call(): # Mock ShaderManager and OpenGL functions with patch("src.gui_2.ShaderManager") as mock_sm_class, \ patch("src.gui_2.gl") as mock_gl, \ - patch("src.gui_2.imgui") as mock_imgui: + patch("src.gui_2.imgui") as mock_imgui, \ + patch("src.gui_2.theme") as mock_theme: mock_sm = mock_sm_class.return_value - mock_sm.fbo_width = 0 - mock_sm.fbo_height = 0 - mock_sm.capture_tex = 1 mock_sm.blur_tex = 2 + mock_theme.get_frosted_glass_enabled.return_value = True mock_imgui.get_io().display_size = MagicMock(x=1920, y=1080) from src.gui_2 import App - app = App() + with patch.object(App, '__init__', return_value=None): + app = App() + app.shader_manager = mock_sm + + # Simulate frame + app._render_frosted_background(pos=MagicMock(x=10, y=10), size=MagicMock(x=100, y=100)) + + # Now it should only call add_image + assert mock_imgui.get_window_draw_list().add_image.called + # It no longer calls these + assert not mock_sm.setup_capture_fbo.called + assert not mock_sm.render_blur.called + assert not mock_sm.capture_begin.called + assert not mock_sm.capture_end.called + +def test_gui_global_blur_call(): + with patch("src.gui_2.ShaderManager") as mock_sm_class, \ + patch("src.gui_2.imgui") as mock_imgui, \ + patch("src.gui_2.theme") as mock_theme, \ + patch("src.gui_2.bg_shader") as mock_bg: - # Simulate frame - app._render_frosted_background(pos=MagicMock(x=10, y=10), size=MagicMock(x=100, y=100)) + mock_sm = mock_sm_class.return_value + mock_theme.get_frosted_glass_enabled.return_value = True + mock_theme.is_nerv_active.return_value = False + mock_imgui.get_io().display_size = MagicMock(x=1920, y=1080) + mock_bg.get_bg.return_value = MagicMock(enabled=False) - assert mock_sm.setup_capture_fbo.called - assert mock_gl.glCopyTexImage2D.called - assert mock_sm.render_blur.called - assert mock_sm.capture_begin.called - assert mock_sm.capture_end.called + from src.gui_2 import App + with patch.object(App, '__init__', return_value=None): + app = App() + app.shader_manager = mock_sm + app.shader_uniforms = { + 'frosted_blur_radius': 10.0, + 'frosted_tint_intensity': 0.5, + 'frosted_opacity': 0.8 + } + app.ai_status = "idle" + app.ui_crt_filter = False + app.controller = MagicMock() + app.perf_profiling_enabled = False + app.is_viewing_prior_session = False + app.config = {} + app.project = {} + app.show_windows = {} + app.ui_auto_scroll_comms = False + app._comms_log_dirty = False + app._tool_log_dirty = False + app._pending_comms_lock = MagicMock() + app._pending_comms = [] + app.ui_focus_agent = None + app._last_ui_focus_agent = None + app.perf_monitor = MagicMock() + app._process_pending_gui_tasks = MagicMock() + app._process_pending_history_adds = MagicMock() + app._render_track_proposal_modal = MagicMock() + app._render_patch_modal = MagicMock() + app._render_save_preset_modal = MagicMock() + app._render_preset_manager_window = MagicMock() + app._render_tool_preset_manager_window = MagicMock() + app._render_persona_editor_window = MagicMock() + app._last_autosave = time.time() + app._autosave_interval = 60 + app._render_custom_title_bar = MagicMock() + app._render_shader_live_editor = MagicMock() + + try: + app._gui_func() + except Exception: + pass + + assert mock_sm.prepare_global_blur.called