Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 273fcf29f1 | |||
| 1eed009b12 | |||
| aed461ef28 | |||
| 1d36357c64 | |||
| 3113e4137b | |||
| cf5eac8c43 | |||
| db00fba836 | |||
| a862119922 | |||
| e6a57cddc2 | |||
| 928318fd06 | |||
| 5416546207 | |||
| 9c2078ad78 | |||
| ab44102bad | |||
| c8b7fca368 | |||
| b3e6590cb4 | |||
| d85dc3a1b3 | |||
| 2947948ac6 | |||
| d9148acb0c |
@@ -1,7 +1,6 @@
|
||||
---
|
||||
description: Tier 2 Tech Lead for architectural design and track execution with persistent memory
|
||||
mode: primary
|
||||
model: MiniMax-M2.5
|
||||
temperature: 0.4
|
||||
permission:
|
||||
edit: ask
|
||||
@@ -14,9 +13,9 @@ ONLY output the requested text. No pleasantries.
|
||||
|
||||
## Context Management
|
||||
|
||||
**MANUAL COMPACTION ONLY** — Never rely on automatic context summarization.
|
||||
**MANUAL COMPACTION ONLY** � Never rely on automatic context summarization.
|
||||
Use `/compact` command explicitly when context needs reduction.
|
||||
You maintain PERSISTENT MEMORY throughout track execution — do NOT apply Context Amnesia to your own session.
|
||||
You maintain PERSISTENT MEMORY throughout track execution � do NOT apply Context Amnesia to your own session.
|
||||
|
||||
## CRITICAL: MCP Tools Only (Native Tools Banned)
|
||||
|
||||
@@ -134,14 +133,14 @@ Before implementing:
|
||||
- Zero-assertion ban: Tests MUST have meaningful assertions
|
||||
- Delegate test creation to Tier 3 Worker via Task tool
|
||||
- Run tests and confirm they FAIL as expected
|
||||
- **CONFIRM FAILURE** — this is the Red phase
|
||||
- **CONFIRM FAILURE** � this is the Red phase
|
||||
|
||||
### 3. Green Phase: Implement to Pass
|
||||
|
||||
- **Pre-delegation checkpoint**: Stage current progress (`git add .`)
|
||||
- Delegate implementation to Tier 3 Worker via Task tool
|
||||
- Run tests and confirm they PASS
|
||||
- **CONFIRM PASS** — this is the Green phase
|
||||
- **CONFIRM PASS** � this is the Green phase
|
||||
|
||||
### 4. Refactor Phase (Optional)
|
||||
|
||||
|
||||
+2
-1
@@ -82,8 +82,9 @@ This file tracks all major tracks for the project. Each track has its own detail
|
||||
11. [ ] **Track: Advanced Text Viewer with Syntax Highlighting**
|
||||
*Link: [./tracks/text_viewer_rich_rendering_20260313/](./tracks/text_viewer_rich_rendering_20260313/)*
|
||||
|
||||
12. [ ] **Track: Frosted Glass Background Effect**
|
||||
12. [ ] ~~**Track: Frosted Glass Background Effect**~~ THIS IS A LOST CAUSE DON'T BOTHER.
|
||||
*Link: [./tracks/frosted_glass_20260313/](./tracks/frosted_glass_20260313/)*
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
# Implementation Plan: Frosted Glass Background Effect (REPAIR - TRUE GPU)
|
||||
|
||||
## Phase 1: Robust Shader & FBO Foundation
|
||||
- [ ] Task: Implement: Create `ShaderManager` methods for downsampled FBO setup (scene, temp, blur).
|
||||
- [ ] Task: Implement: Develop the "Deep Sea" background shader and integrate it as the FBO source.
|
||||
- [ ] Task: Implement: Develop the 2-pass Gaussian blur shaders with a wide tap distribution.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Robust Foundation' (Protocol in workflow.md)
|
||||
- [x] Task: Implement: Create `ShaderManager` methods for downsampled FBO setup (scene, temp, blur). [d9148ac]
|
||||
- [x] Task: Implement: Develop the "Deep Sea" background shader and integrate it as the FBO source. [d85dc3a]
|
||||
- [x] Task: Implement: Develop the 2-pass Gaussian blur shaders with a wide tap distribution. [c8b7fca]
|
||||
- [x] Task: Conductor - User Manual Verification 'Phase 1: Robust Foundation' (Protocol in workflow.md)
|
||||
|
||||
## Phase 2: High-Performance Blur Pipeline
|
||||
- [ ] Task: Implement: Create the `prepare_global_blur` method that renders the background and blurs it at 1/4 resolution.
|
||||
- [ ] Task: Implement: Ensure the pipeline correctly handles high-DPI scaling (`fb_scale`) for internal FBO dimensions.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 2: High-Performance Pipeline' (Protocol in workflow.md)
|
||||
- [x] Task: Implement: Create the `prepare_global_blur` method that renders the background and blurs it at 1/4 resolution. [9c2078a]
|
||||
- [x] Task: Implement: Ensure the pipeline correctly handles high-DPI scaling (`fb_scale`) for internal FBO dimensions. [9c2078a]
|
||||
- [x] Task: Conductor - User Manual Verification 'Phase 2: High-Performance Pipeline' (Protocol in workflow.md)
|
||||
|
||||
## Phase 3: GUI Integration & Screen-Space Sampling
|
||||
- [ ] Task: Implement: Update `_render_frosted_background` to perform normalized screen-space UV sampling.
|
||||
- [ ] Task: Implement: Update `_begin_window` and `_end_window` to manage global transparency and call the blur renderer.
|
||||
- [ ] Task: Implement: Apply the new window wrappers to all primary panels in `src/gui_2.py`.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 3: GUI Integration' (Protocol in workflow.md)
|
||||
|
||||
## Phase 4: UI Tuning & Persistence
|
||||
- [ ] Task: Implement: Add "Frosted Glass" tuning sliders to the Shader Editor.
|
||||
- [ ] Task: Implement: Update `src/theme_2.py` to persist all frosted glass settings.
|
||||
- [ ] Task: Verify: Confirm zero recursion, zero attribute errors, and stable style stack.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Final Polishing' (Protocol in workflow.md)
|
||||
- [x] Task: Implement: Update `_render_frosted_background` to perform normalized screen-space UV sampling. [926318f]
|
||||
- [x] Task: Fix crash when display_size is invalid at startup. [db00fba]
|
||||
## Phase 3: GUI Integration & Screen-Space Sampling
|
||||
- [x] Task: Implement: Update `_render_frosted_background` to perform normalized screen-space UV sampling. [a862119]
|
||||
- [~] Task: Implement: Update `_begin_window` and `_end_window` to manage global transparency and call the blur renderer.
|
||||
|
||||
+7
-7
@@ -23,15 +23,15 @@ active = "C:/projects/gencpp/gencpp_sloppy.toml"
|
||||
separate_message_panel = false
|
||||
separate_response_panel = false
|
||||
separate_tool_calls_panel = false
|
||||
bg_shader_enabled = true
|
||||
bg_shader_enabled = false
|
||||
crt_filter_enabled = false
|
||||
separate_task_dag = false
|
||||
separate_usage_analytics = false
|
||||
separate_usage_analytics = true
|
||||
separate_tier1 = false
|
||||
separate_tier2 = false
|
||||
separate_tier3 = false
|
||||
separate_tier4 = false
|
||||
separate_external_tools = false
|
||||
separate_external_tools = true
|
||||
|
||||
[gui.show_windows]
|
||||
"Context Hub" = true
|
||||
@@ -39,7 +39,7 @@ separate_external_tools = false
|
||||
"AI Settings" = true
|
||||
"MMA Dashboard" = true
|
||||
"Task DAG" = false
|
||||
"Usage Analytics" = false
|
||||
"Usage Analytics" = true
|
||||
"Tier 1" = false
|
||||
"Tier 2" = false
|
||||
"Tier 3" = false
|
||||
@@ -57,15 +57,15 @@ Theme = true
|
||||
"Log Management" = true
|
||||
Diagnostics = false
|
||||
"External Tools" = false
|
||||
"Shader Editor" = false
|
||||
"Shader Editor" = true
|
||||
|
||||
[theme]
|
||||
palette = "Nord Dark"
|
||||
font_path = "C:/projects/manual_slop/assets/fonts/MapleMono-Regular.ttf"
|
||||
font_size = 18.0
|
||||
scale = 1.0
|
||||
transparency = 0.5400000214576721
|
||||
child_transparency = 0.5899999737739563
|
||||
transparency = 0.4399999976158142
|
||||
child_transparency = 0.5099999904632568
|
||||
|
||||
[mma]
|
||||
max_workers = 4
|
||||
|
||||
+30
-33
@@ -74,8 +74,8 @@ Collapsed=0
|
||||
DockId=0xAFC85805,2
|
||||
|
||||
[Window][Theme]
|
||||
Pos=0,703
|
||||
Size=630,737
|
||||
Pos=0,543
|
||||
Size=387,737
|
||||
Collapsed=0
|
||||
DockId=0x00000002,2
|
||||
|
||||
@@ -91,8 +91,8 @@ Collapsed=0
|
||||
DockId=0x00000010,2
|
||||
|
||||
[Window][Context Hub]
|
||||
Pos=0,703
|
||||
Size=630,737
|
||||
Pos=0,543
|
||||
Size=387,737
|
||||
Collapsed=0
|
||||
DockId=0x00000002,1
|
||||
|
||||
@@ -103,26 +103,26 @@ Collapsed=0
|
||||
DockId=0x0000000D,0
|
||||
|
||||
[Window][Discussion Hub]
|
||||
Pos=1263,22
|
||||
Size=709,1418
|
||||
Pos=1169,26
|
||||
Size=950,1254
|
||||
Collapsed=0
|
||||
DockId=0x00000013,0
|
||||
|
||||
[Window][Operations Hub]
|
||||
Pos=632,22
|
||||
Size=629,1418
|
||||
Pos=389,26
|
||||
Size=778,1254
|
||||
Collapsed=0
|
||||
DockId=0x00000005,0
|
||||
|
||||
[Window][Files & Media]
|
||||
Pos=0,703
|
||||
Size=630,737
|
||||
Pos=0,543
|
||||
Size=387,737
|
||||
Collapsed=0
|
||||
DockId=0x00000002,0
|
||||
|
||||
[Window][AI Settings]
|
||||
Pos=0,22
|
||||
Size=630,679
|
||||
Pos=0,26
|
||||
Size=387,515
|
||||
Collapsed=0
|
||||
DockId=0x00000001,0
|
||||
|
||||
@@ -132,14 +132,14 @@ Size=416,325
|
||||
Collapsed=0
|
||||
|
||||
[Window][MMA Dashboard]
|
||||
Pos=1974,22
|
||||
Size=586,1418
|
||||
Pos=2121,26
|
||||
Size=653,1254
|
||||
Collapsed=0
|
||||
DockId=0x00000010,0
|
||||
|
||||
[Window][Log Management]
|
||||
Pos=1974,22
|
||||
Size=586,1418
|
||||
Pos=2121,26
|
||||
Size=653,1254
|
||||
Collapsed=0
|
||||
DockId=0x00000010,1
|
||||
|
||||
@@ -167,7 +167,7 @@ Collapsed=0
|
||||
Pos=2822,1717
|
||||
Size=1018,420
|
||||
Collapsed=0
|
||||
DockId=0x0000000C,0
|
||||
DockId=0x00000011,0
|
||||
|
||||
[Window][Approve PowerShell Command]
|
||||
Pos=649,435
|
||||
@@ -330,10 +330,9 @@ Size=967,499
|
||||
Collapsed=0
|
||||
|
||||
[Window][Usage Analytics]
|
||||
Pos=1739,1107
|
||||
Size=586,269
|
||||
Pos=1627,680
|
||||
Size=480,343
|
||||
Collapsed=0
|
||||
DockId=0x0000000F,0
|
||||
|
||||
[Window][Tool Preset Manager]
|
||||
Pos=1301,302
|
||||
@@ -351,7 +350,7 @@ Size=1000,800
|
||||
Collapsed=0
|
||||
|
||||
[Window][External Tools]
|
||||
Pos=531,376
|
||||
Pos=1968,516
|
||||
Size=616,409
|
||||
Collapsed=0
|
||||
|
||||
@@ -381,8 +380,8 @@ Size=900,700
|
||||
Collapsed=0
|
||||
|
||||
[Window][Shader Editor]
|
||||
Pos=457,710
|
||||
Size=493,252
|
||||
Pos=998,497
|
||||
Size=493,369
|
||||
Collapsed=0
|
||||
|
||||
[Table][0xFB6E3870,4]
|
||||
@@ -498,23 +497,21 @@ Column 1 Weight=1.0000
|
||||
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
|
||||
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
||||
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
||||
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,22 Size=2560,1418 Split=X
|
||||
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=1640,1183 Split=X
|
||||
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,26 Size=2774,1254 Split=X
|
||||
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=1980,1183 Split=X
|
||||
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
|
||||
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=630,858 Split=Y Selected=0x8CA2375C
|
||||
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=680,858 Split=Y Selected=0x8CA2375C
|
||||
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=824,525 CentralNode=1 Selected=0x7BD57D6A
|
||||
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=824,737 Selected=0x8CA2375C
|
||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1340,858 Split=X Selected=0x418C7449
|
||||
DockNode ID=0x00000012 Parent=0x0000000E SizeRef=629,402 Split=Y Selected=0x418C7449
|
||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1730,858 Split=X Selected=0x418C7449
|
||||
DockNode ID=0x00000012 Parent=0x0000000E SizeRef=778,402 Split=Y Selected=0x418C7449
|
||||
DockNode ID=0x00000005 Parent=0x00000012 SizeRef=876,1749 Selected=0x418C7449
|
||||
DockNode ID=0x00000006 Parent=0x00000012 SizeRef=876,362 Selected=0x1D56B311
|
||||
DockNode ID=0x00000013 Parent=0x0000000E SizeRef=709,402 Selected=0x6F2B5B04
|
||||
DockNode ID=0x00000013 Parent=0x0000000E SizeRef=950,402 Selected=0x6F2B5B04
|
||||
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
|
||||
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=586,1183 Split=Y Selected=0x3AEC3498
|
||||
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=653,1183 Split=Y Selected=0x3AEC3498
|
||||
DockNode ID=0x00000010 Parent=0x00000004 SizeRef=1199,1689 Selected=0x2C0206CE
|
||||
DockNode ID=0x00000011 Parent=0x00000004 SizeRef=1199,420 Split=X Selected=0xDEB547B6
|
||||
DockNode ID=0x0000000C Parent=0x00000011 SizeRef=916,380 Selected=0x655BC6E9
|
||||
DockNode ID=0x0000000F Parent=0x00000011 SizeRef=281,380 Selected=0xDEB547B6
|
||||
DockNode ID=0x00000011 Parent=0x00000004 SizeRef=1199,420 Selected=0xDEB547B6
|
||||
|
||||
;;;<<<Layout_655921752_Default>>>;;;
|
||||
;;;<<<HelloImGui_Misc>>>;;;
|
||||
|
||||
@@ -39,6 +39,7 @@ else:
|
||||
|
||||
from pydantic import BaseModel
|
||||
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"]
|
||||
COMMS_CLAMP_CHARS: int = 300
|
||||
@@ -212,6 +213,33 @@ class App:
|
||||
self.ui_tool_filter_category = "All"
|
||||
self.ui_discussion_split_h = 300.0
|
||||
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_custom_background(self):
|
||||
return # DISABLED - imgui-bundle can't sample OpenGL textures
|
||||
|
||||
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:
|
||||
"""UI-level wrapper for approving a pending tool execution ask."""
|
||||
@@ -437,6 +465,8 @@ class App:
|
||||
exp, opened = imgui.begin('Shader Editor', self.show_windows['Shader Editor'])
|
||||
self.show_windows['Shader Editor'] = bool(opened)
|
||||
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_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)
|
||||
@@ -3994,6 +4024,36 @@ def hello():
|
||||
def _post_init(self) -> None:
|
||||
theme.apply_current()
|
||||
|
||||
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
|
||||
if ws.x <= 0 or ws.y <= 0:
|
||||
return False
|
||||
if fb_scale <= 0:
|
||||
fb_scale = 1.0
|
||||
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()
|
||||
return True
|
||||
|
||||
def _pre_new_frame(self) -> None:
|
||||
if not self.ui_frosted_glass_enabled:
|
||||
return
|
||||
ws = imgui.get_io().display_size
|
||||
fb_scale = imgui.get_io().display_framebuffer_scale.x
|
||||
if ws.x <= 0 or ws.y <= 0:
|
||||
return
|
||||
if fb_scale <= 0:
|
||||
fb_scale = 1.0
|
||||
if self._blur_pipeline is None:
|
||||
if not self._init_blur_pipeline():
|
||||
return
|
||||
import time
|
||||
t = time.time()
|
||||
self._blur_pipeline.prepare_global_blur(int(ws.x), int(ws.y), t, fb_scale)
|
||||
|
||||
def run(self) -> None:
|
||||
"""Initializes the ImGui runner and starts the main application loop."""
|
||||
if "--headless" in sys.argv:
|
||||
@@ -4049,6 +4109,8 @@ def hello():
|
||||
self.runner_params.callbacks.load_additional_fonts = self._load_fonts
|
||||
self.runner_params.callbacks.setup_imgui_style = theme.apply_current
|
||||
self.runner_params.callbacks.post_init = self._post_init
|
||||
self.runner_params.callbacks.pre_new_frame = self._pre_new_frame
|
||||
self.runner_params.callbacks.custom_background = self._render_custom_background
|
||||
self._fetch_models(self.current_provider)
|
||||
md_options = markdown_helper.get_renderer().options
|
||||
immapp.run(self.runner_params, add_ons_params=immapp.AddOnsParams(with_markdown_options=md_options))
|
||||
|
||||
@@ -150,4 +150,325 @@ void main() {
|
||||
gl.glUniform1f(u_time_loc, float(time))
|
||||
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
||||
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
|
||||
|
||||
class BlurPipeline:
|
||||
def __init__(self):
|
||||
self.scene_fbo: int | None = None
|
||||
self.scene_tex: int | None = None
|
||||
self.blur_fbo_a: int | None = None
|
||||
self.blur_tex_a: int | None = None
|
||||
self.blur_fbo_b: int | None = None
|
||||
self.blur_tex_b: int | None = None
|
||||
self.h_blur_program: int | None = None
|
||||
self.v_blur_program: int | None = None
|
||||
self.deepsea_program: int | None = None
|
||||
self._quad_vao: int | None = None
|
||||
self._fb_width: int = 0
|
||||
self._fb_height: int = 0
|
||||
self._fb_scale: int = 1
|
||||
|
||||
def _compile_shader(self, vertex_src: str, fragment_src: str) -> int:
|
||||
program = gl.glCreateProgram()
|
||||
def _compile(src, shader_type):
|
||||
shader = gl.glCreateShader(shader_type)
|
||||
gl.glShaderSource(shader, src)
|
||||
gl.glCompileShader(shader)
|
||||
if not gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS):
|
||||
info_log = gl.glGetShaderInfoLog(shader)
|
||||
if hasattr(info_log, "decode"):
|
||||
info_log = info_log.decode()
|
||||
raise RuntimeError(f"Shader compilation failed: {info_log}")
|
||||
return shader
|
||||
vert_shader = _compile(vertex_src, gl.GL_VERTEX_SHADER)
|
||||
frag_shader = _compile(fragment_src, gl.GL_FRAGMENT_SHADER)
|
||||
gl.glAttachShader(program, vert_shader)
|
||||
gl.glAttachShader(program, frag_shader)
|
||||
gl.glLinkProgram(program)
|
||||
if not gl.glGetProgramiv(program, gl.GL_LINK_STATUS):
|
||||
info_log = gl.glGetProgramInfoLog(program)
|
||||
if hasattr(info_log, "decode"):
|
||||
info_log = info_log.decode()
|
||||
raise RuntimeError(f"Program linking failed: {info_log}")
|
||||
gl.glDeleteShader(vert_shader)
|
||||
gl.glDeleteShader(frag_shader)
|
||||
return program
|
||||
|
||||
def _create_fbo(self, width: int, height: int) -> tuple[int, int]:
|
||||
if width <= 0 or height <= 0:
|
||||
raise ValueError(f"Invalid FBO dimensions: {width}x{height}")
|
||||
tex = gl.glGenTextures(1)
|
||||
gl.glBindTexture(gl.GL_TEXTURE_2D, tex)
|
||||
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA8, 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)
|
||||
fbo = gl.glGenFramebuffers(1)
|
||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, fbo)
|
||||
gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, tex, 0)
|
||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
|
||||
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
|
||||
return fbo, tex
|
||||
|
||||
def _create_quad_vao(self) -> int:
|
||||
import ctypes
|
||||
vao = gl.glGenVertexArrays(1)
|
||||
gl.glBindVertexArray(vao)
|
||||
vertices = (ctypes.c_float * 16)(
|
||||
-1.0, -1.0, 0.0, 0.0,
|
||||
1.0, -1.0, 1.0, 0.0,
|
||||
-1.0, 1.0, 0.0, 1.0,
|
||||
1.0, 1.0, 1.0, 1.0
|
||||
)
|
||||
vbo = gl.glGenBuffers(1)
|
||||
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo)
|
||||
gl.glBufferData(gl.GL_ARRAY_BUFFER, ctypes.sizeof(vertices), vertices, gl.GL_STATIC_DRAW)
|
||||
gl.glEnableVertexAttribArray(0)
|
||||
gl.glVertexAttribPointer(0, 2, gl.GL_FLOAT, gl.GL_FALSE, 16, None)
|
||||
gl.glEnableVertexAttribArray(1)
|
||||
gl.glVertexAttribPointer(1, 2, gl.GL_FLOAT, gl.GL_FALSE, 16, ctypes.c_void_p(8))
|
||||
gl.glBindVertexArray(0)
|
||||
return vao
|
||||
|
||||
def setup_fbos(self, width: int, height: int, fb_scale: float = 1.0):
|
||||
scale = max(1, int(fb_scale))
|
||||
blur_w = max(1, (width * scale) // 4)
|
||||
blur_h = max(1, (height * scale) // 4)
|
||||
self._fb_width = blur_w
|
||||
self._fb_height = blur_h
|
||||
self._fb_scale = scale
|
||||
scene_w = width * scale
|
||||
scene_h = height * scale
|
||||
self.scene_fbo, self.scene_tex = self._create_fbo(scene_w, scene_h)
|
||||
self.blur_fbo_a, self.blur_tex_a = self._create_fbo(blur_w, blur_h)
|
||||
self.blur_fbo_b, self.blur_tex_b = self._create_fbo(blur_w, blur_h)
|
||||
|
||||
def compile_blur_shaders(self):
|
||||
vert_src = """
|
||||
#version 330 core
|
||||
layout(location = 0) in vec2 a_position;
|
||||
layout(location = 1) in vec2 a_texcoord;
|
||||
out vec2 v_uv;
|
||||
void main() {
|
||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||
v_uv = a_texcoord;
|
||||
}
|
||||
"""
|
||||
h_frag_src = """
|
||||
#version 330 core
|
||||
in vec2 v_uv;
|
||||
uniform sampler2D u_texture;
|
||||
uniform vec2 u_texel_size;
|
||||
out vec4 FragColor;
|
||||
void main() {
|
||||
vec2 offset = vec2(u_texel_size.x, 0.0);
|
||||
vec4 sum = vec4(0.0);
|
||||
sum += texture(u_texture, v_uv - offset * 6.0) * 0.0152;
|
||||
sum += texture(u_texture, v_uv - offset * 5.0) * 0.0300;
|
||||
sum += texture(u_texture, v_uv - offset * 4.0) * 0.0525;
|
||||
sum += texture(u_texture, v_uv - offset * 3.0) * 0.0812;
|
||||
sum += texture(u_texture, v_uv - offset * 2.0) * 0.1110;
|
||||
sum += texture(u_texture, v_uv - offset * 1.0) * 0.1342;
|
||||
sum += texture(u_texture, v_uv) * 0.1432;
|
||||
sum += texture(u_texture, v_uv + offset * 1.0) * 0.1342;
|
||||
sum += texture(u_texture, v_uv + offset * 2.0) * 0.1110;
|
||||
sum += texture(u_texture, v_uv + offset * 3.0) * 0.0812;
|
||||
sum += texture(u_texture, v_uv + offset * 4.0) * 0.0525;
|
||||
sum += texture(u_texture, v_uv + offset * 5.0) * 0.0300;
|
||||
sum += texture(u_texture, v_uv + offset * 6.0) * 0.0152;
|
||||
FragColor = sum;
|
||||
}
|
||||
"""
|
||||
v_frag_src = """
|
||||
#version 330 core
|
||||
in vec2 v_uv;
|
||||
uniform sampler2D u_texture;
|
||||
uniform vec2 u_texel_size;
|
||||
out vec4 FragColor;
|
||||
void main() {
|
||||
vec2 offset = vec2(0.0, u_texel_size.y);
|
||||
vec4 sum = vec4(0.0);
|
||||
sum += texture(u_texture, v_uv - offset * 6.0) * 0.0152;
|
||||
sum += texture(u_texture, v_uv - offset * 5.0) * 0.0300;
|
||||
sum += texture(u_texture, v_uv - offset * 4.0) * 0.0525;
|
||||
sum += texture(u_texture, v_uv - offset * 3.0) * 0.0812;
|
||||
sum += texture(u_texture, v_uv - offset * 2.0) * 0.1110;
|
||||
sum += texture(u_texture, v_uv - offset * 1.0) * 0.1342;
|
||||
sum += texture(u_texture, v_uv) * 0.1432;
|
||||
sum += texture(u_texture, v_uv + offset * 1.0) * 0.1342;
|
||||
sum += texture(u_texture, v_uv + offset * 2.0) * 0.1110;
|
||||
sum += texture(u_texture, v_uv + offset * 3.0) * 0.0812;
|
||||
sum += texture(u_texture, v_uv + offset * 4.0) * 0.0525;
|
||||
sum += texture(u_texture, v_uv + offset * 5.0) * 0.0300;
|
||||
sum += texture(u_texture, v_uv + offset * 6.0) * 0.0152;
|
||||
FragColor = sum;
|
||||
}
|
||||
"""
|
||||
self.h_blur_program = self._compile_shader(vert_src, h_frag_src)
|
||||
self.v_blur_program = self._compile_shader(vert_src, v_frag_src)
|
||||
|
||||
def compile_deepsea_shader(self):
|
||||
vert_src = """
|
||||
#version 330 core
|
||||
layout(location = 0) in vec2 a_position;
|
||||
layout(location = 1) in vec2 a_texcoord;
|
||||
out vec2 v_uv;
|
||||
void main() {
|
||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||
v_uv = a_texcoord;
|
||||
}
|
||||
"""
|
||||
frag_src = """
|
||||
#version 330 core
|
||||
in vec2 v_uv;
|
||||
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.5453);
|
||||
}
|
||||
|
||||
float noise(vec2 p) {
|
||||
vec2 i = floor(p);
|
||||
vec2 f = fract(p);
|
||||
f = f * f * (3.0 - 2.0 * f);
|
||||
float a = hash(i);
|
||||
float b = hash(i + vec2(1.0, 0.0));
|
||||
float c = hash(i + vec2(0.0, 1.0));
|
||||
float d = hash(i + vec2(1.0, 1.0));
|
||||
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
||||
}
|
||||
|
||||
float fbm(vec2 p) {
|
||||
float v = 0.0;
|
||||
float a = 0.5;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
v += a * noise(p);
|
||||
p *= 2.0;
|
||||
a *= 0.5;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = v_uv;
|
||||
float t = u_time * 0.3;
|
||||
vec3 col = vec3(0.01, 0.05, 0.12);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
float phase = t * (0.1 + float(i) * 0.05);
|
||||
vec2 blob_uv = uv + vec2(sin(phase), cos(phase * 0.8)) * 0.3;
|
||||
float blob = fbm(blob_uv * 3.0 + t * 0.2);
|
||||
col = mix(col, vec3(0.02, 0.20, 0.40), blob * 0.4);
|
||||
}
|
||||
float line_alpha = 0.0;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
float fi = float(i);
|
||||
float offset = mod(t * 15.0 + fi * (u_resolution.x / 12.0), u_resolution.x);
|
||||
float line_x = offset / u_resolution.x;
|
||||
float dist = abs(uv.x - line_x);
|
||||
float alpha = smoothstep(0.02, 0.0, dist) * (0.1 + 0.05 * sin(t + fi));
|
||||
line_alpha += alpha;
|
||||
}
|
||||
col += vec3(0.04, 0.35, 0.55) * line_alpha;
|
||||
float vignette = 1.0 - length(uv - 0.5) * 0.8;
|
||||
col *= vignette;
|
||||
FragColor = vec4(col, 1.0);
|
||||
}
|
||||
"""
|
||||
self.deepsea_program = self._compile_shader(vert_src, frag_src)
|
||||
self._quad_vao = self._create_quad_vao()
|
||||
|
||||
def render_deepsea_to_fbo(self, width: int, height: int, time: float):
|
||||
if not self.deepsea_program or not self.scene_fbo or not self._quad_vao:
|
||||
return
|
||||
scene_w = width * self._fb_scale
|
||||
scene_h = height * self._fb_scale
|
||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.scene_fbo)
|
||||
gl.glViewport(0, 0, scene_w, scene_h)
|
||||
gl.glClearColor(0.01, 0.05, 0.12, 1.0)
|
||||
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
|
||||
gl.glUseProgram(self.deepsea_program)
|
||||
u_time_loc = gl.glGetUniformLocation(self.deepsea_program, "u_time")
|
||||
if u_time_loc != -1:
|
||||
gl.glUniform1f(u_time_loc, time)
|
||||
u_res_loc = gl.glGetUniformLocation(self.deepsea_program, "u_resolution")
|
||||
if u_res_loc != -1:
|
||||
gl.glUniform2f(u_res_loc, float(scene_w), float(scene_h))
|
||||
gl.glBindVertexArray(self._quad_vao)
|
||||
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
||||
gl.glBindVertexArray(0)
|
||||
gl.glUseProgram(0)
|
||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
|
||||
|
||||
def _render_quad(self, program: int, src_tex: int, texel_size: tuple[float, float]):
|
||||
gl.glUseProgram(program)
|
||||
gl.glActiveTexture(gl.GL_TEXTURE0)
|
||||
gl.glBindTexture(gl.GL_TEXTURE_2D, src_tex)
|
||||
u_tex = gl.glGetUniformLocation(program, "u_texture")
|
||||
if u_tex != -1:
|
||||
gl.glUniform1i(u_tex, 0)
|
||||
u_ts = gl.glGetUniformLocation(program, "u_texel_size")
|
||||
if u_ts != -1:
|
||||
gl.glUniform2f(u_ts, texel_size[0], texel_size[1])
|
||||
gl.glBindVertexArray(self._quad_vao)
|
||||
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
||||
gl.glBindVertexArray(0)
|
||||
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
|
||||
gl.glUseProgram(0)
|
||||
|
||||
def prepare_blur(self, width: int, height: int, time: float):
|
||||
if not self.h_blur_program or not self.v_blur_program:
|
||||
return
|
||||
if not self.blur_fbo_a or not self.blur_fbo_b:
|
||||
return
|
||||
blur_w = max(1, self._fb_width)
|
||||
blur_h = max(1, self._fb_height)
|
||||
texel_x = 1.0 / float(blur_w)
|
||||
texel_y = 1.0 / float(blur_h)
|
||||
gl.glViewport(0, 0, blur_w, blur_h)
|
||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo_a)
|
||||
gl.glClearColor(0.0, 0.0, 0.0, 0.0)
|
||||
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
|
||||
self._render_quad(self.h_blur_program, self.scene_tex, (texel_x, texel_y))
|
||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo_b)
|
||||
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
|
||||
self._render_quad(self.v_blur_program, self.blur_tex_a, (texel_x, texel_y))
|
||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
|
||||
restore_w = width * self._fb_scale
|
||||
restore_h = height * self._fb_scale
|
||||
gl.glViewport(0, 0, restore_w, restore_h)
|
||||
|
||||
def prepare_global_blur(self, width: int, height: int, time: float, fb_scale: float = 1.0):
|
||||
if not self.scene_fbo:
|
||||
if self._fb_scale != int(fb_scale):
|
||||
self.setup_fbos(width, height, fb_scale)
|
||||
self.render_deepsea_to_fbo(width, height, time)
|
||||
self.prepare_blur(width, height, time)
|
||||
|
||||
def get_blur_texture(self) -> int | None:
|
||||
return self.blur_tex_b
|
||||
|
||||
def cleanup(self):
|
||||
fbos = [f for f in [self.scene_fbo, self.blur_fbo_a, self.blur_fbo_b] if f is not None]
|
||||
texs = [t for t in [self.scene_tex, self.blur_tex_a, self.blur_tex_b] if t is not None]
|
||||
progs = [p for p in [self.h_blur_program, self.v_blur_program, self.deepsea_program] if p is not None]
|
||||
if fbos:
|
||||
gl.glDeleteFramebuffers(len(fbos), fbos)
|
||||
if texs:
|
||||
gl.glDeleteTextures(len(texs), texs)
|
||||
if progs:
|
||||
for p in progs:
|
||||
gl.glDeleteProgram(p)
|
||||
if self._quad_vao:
|
||||
gl.glDeleteVertexArrays(1, [self._quad_vao])
|
||||
self.scene_fbo = None
|
||||
self.scene_tex = None
|
||||
self.blur_fbo_a = None
|
||||
self.blur_tex_a = None
|
||||
self.blur_fbo_b = None
|
||||
self.blur_tex_b = None
|
||||
self.h_blur_program = None
|
||||
self.v_blur_program = None
|
||||
self.deepsea_program = None
|
||||
self._quad_vao = None
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from src.gui_2 import App
|
||||
|
||||
|
||||
def test_frosted_glass_disabled():
|
||||
with patch("src.gui_2.imgui") as mock_imgui:
|
||||
with patch("src.gui_2.gl") as mock_gl:
|
||||
app = App()
|
||||
app.ui_frosted_glass_enabled = False
|
||||
app._render_frosted_background((0, 0), (100, 100))
|
||||
assert app._blur_pipeline is None
|
||||
mock_gl.glEnable.assert_not_called()
|
||||
mock_gl.glBlendFunc.assert_not_called()
|
||||
mock_gl.glBindTexture.assert_not_called()
|
||||
mock_gl.glBegin.assert_not_called()
|
||||
mock_gl.glEnd.assert_not_called()
|
||||
mock_gl.glDisable.assert_not_called()
|
||||
mock_imgui.get_io().display_size.assert_not_called()
|
||||
mock_imgui.get_io().display_framebuffer_scale.assert_not_called()
|
||||
mock_imgui.get_window_draw_list.assert_not_called()
|
||||
mock_imgui.get_window_pos.assert_not_called()
|
||||
mock_imgui.get_window_size.assert_not_called()
|
||||
mock_imgui.get_color_u32.assert_not_called()
|
||||
mock_imgui.push_texture_id.assert_not_called()
|
||||
mock_imgui.pop_texture_id.assert_not_called()
|
||||
@@ -26,5 +26,84 @@ def test_gui2_old_windows_removed_from_show_windows(app_instance: App) -> None:
|
||||
"Provider", "System Prompts",
|
||||
"Comms History"
|
||||
]
|
||||
for old_win in old_windows:
|
||||
from src.gui_2 import App
|
||||
|
||||
def test_gui2_hubs_exist_in_show_windows(app_instance: App) -> None:
|
||||
expected_hubs = [
|
||||
"Context Hub",
|
||||
"AI Settings",
|
||||
"Discussion Hub",
|
||||
"Operations Hub",
|
||||
"Files & Media",
|
||||
"Theme",
|
||||
]
|
||||
for hub in expected_hubs:
|
||||
assert hub in app_instance.show_windows, f"Expected hub window '{hub}' not found in show_windows"
|
||||
|
||||
def test_gui2_old_windows_removed_from_show_windows(app_instance: App) -> None:
|
||||
old_windows = [
|
||||
"Projects", "Files", "Screenshots",
|
||||
"Provider", "System Prompts",
|
||||
"Comms History"
|
||||
]
|
||||
for old_win in old_windows:
|
||||
assert old_win not in app_instance.show_windows, f"Old window '{old_win}' should have been removed from show_windows"
|
||||
|
||||
def test_frosted_glass_disabled():
|
||||
with patch("src.gui_2.imgui"):
|
||||
app = App()
|
||||
app.ui_frosted_glass_enabled = False
|
||||
app._render_frosted_background((0, 0), (100, 100))
|
||||
assert not app._blur_pipeline is None or not app._blur_pipeline.prepare_global_blur.called
|
||||
imgui.get_io().display_size.assert_not_called()
|
||||
imgui.get_io().display_framebuffer_scale.assert_not_called()
|
||||
imgui.get_window_draw_list.assert_not_called()
|
||||
imgui.get_window_pos.assert_not_called()
|
||||
imgui.get_window_size.assert_not_called()
|
||||
imgui.get_color_u32.assert_not_called()
|
||||
imgui.push_texture_id.assert_not_called()
|
||||
imgui.pop_texture_id.assert_not_called()
|
||||
dl.add_image_quad.assert_not_called()
|
||||
imgui.pop_texture_id.assert_not_called()
|
||||
gl.glEnable.assert_not_called()
|
||||
gl.glBlendFunc.assert_not_called()
|
||||
gl.glBindTexture.assert_not_called()
|
||||
gl.glBegin.assert_not_called()
|
||||
gl.glEnd.assert_not_called()
|
||||
gl.glDisable.assert_not_called()
|
||||
gl.glUnbindTexture.assert_not_called()
|
||||
gl.glDeleteTexture.assert_not_called()
|
||||
gl.glDisable.assert_not_called()
|
||||
|
||||
def test_frosted_glass_enabled():
|
||||
with patch("src.gui_2.imgui"):
|
||||
with patch("src.gui_2.BlurPipeline") as mock_blur:
|
||||
app = App()
|
||||
app.ui_frosted_glass_enabled = True
|
||||
app._blur_pipeline = mock_blur
|
||||
mock_blur.return_value = BlurPipeline()
|
||||
mock_blur.prepare_global_blur.return_value = None
|
||||
mock_blur.get_blur_texture.return_value = 123
|
||||
imgui.get_io().display_size = MagicMock(x=800.0, y=600.0)
|
||||
imgui.get_io().display_framebuffer_scale = MagicMock(x=1.0, y=1.0)
|
||||
imgui.get_window_draw_list.return_value = MagicMock()
|
||||
imgui.get_window_pos.return_value = (100, 200)
|
||||
imgui.get_window_size.return_value = (300, 400)
|
||||
imgui.get_color_u32.return_value = 0xFFFFFFFF
|
||||
dl = MagicMock()
|
||||
imgui.get_window_draw_list.return_value = dl
|
||||
app._render_frosted_background((100, 200), (300, 400))
|
||||
mock_blur.get_blur_texture.assert_called_once()
|
||||
assert dl.add_callback_texture_id.called
|
||||
assert dl.add_callback_quadsDrawElements.called
|
||||
imgui.push_texture_id.assert_called()
|
||||
imgui.pop_texture_id.assert_called()
|
||||
gl.glEnable.assert_called()
|
||||
gl.glBlendFunc.assert_called()
|
||||
gl.glBindTexture.assert_called()
|
||||
gl.glBegin.assert_called()
|
||||
gl.glEnd.assert_called()
|
||||
gl.glDisable.assert_called()
|
||||
gl.glUnbindTexture.assert_called()
|
||||
gl.glDeleteTexture.assert_not_called()
|
||||
|
||||
@@ -1,6 +1,172 @@
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user