Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 273fcf29f1 | |||
| 1eed009b12 | |||
| aed461ef28 | |||
| 1d36357c64 | |||
| 3113e4137b | |||
| cf5eac8c43 | |||
| db00fba836 | |||
| a862119922 | |||
| e6a57cddc2 | |||
| 928318fd06 | |||
| 5416546207 | |||
| 9c2078ad78 | |||
| ab44102bad | |||
| c8b7fca368 | |||
| b3e6590cb4 | |||
| d85dc3a1b3 | |||
| 2947948ac6 | |||
| d9148acb0c | |||
| 2c39f1dcf4 | |||
| 1a8efa880a | |||
| 11eb69449d | |||
| 3a0d388502 | |||
| 879e0991c9 | |||
| d96adca67c | |||
| 4b0ebe44ff | |||
| 6b8151235f | |||
| 69107a75d3 |
@@ -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)
|
||||
|
||||
|
||||
+11
-1
@@ -33,7 +33,10 @@ This file tracks all major tracks for the project. Each track has its own detail
|
||||
*Goal: Integrate Beads (git-backed graph issue tracker) as an alternative backend for MMA implementation tracks and tickets.*
|
||||
|
||||
7. [ ] **Track: Optimization pass for Data-Oriented Python heuristics**
|
||||
*Link: [./tracks/data_oriented_optimization_20260312/](./tracks/data_oriented_optimization_20260312/)*
|
||||
*Link: [./tracks/data_oriented_optimization_20260312/](./tracks/data_oriented_optimization_20260312/)*
|
||||
|
||||
8. [ ] **Track: Rich Thinking Trace Handling**
|
||||
*Link: [./tracks/thinking_trace_handling_20260313/](./tracks/thinking_trace_handling_20260313/)*
|
||||
|
||||
---
|
||||
|
||||
@@ -76,6 +79,13 @@ This file tracks all major tracks for the project. Each track has its own detail
|
||||
*Link: [./tracks/undo_redo_history_20260311/](./tracks/undo_redo_history_20260311/)*
|
||||
*Goal: Robust, non-provider based undo/redo for text inputs, UI controls, discussion mutations, and context management. Includes hotkey support and a history list view.*
|
||||
|
||||
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**~~ THIS IS A LOST CAUSE DON'T BOTHER.
|
||||
*Link: [./tracks/frosted_glass_20260313/](./tracks/frosted_glass_20260313/)*
|
||||
|
||||
|
||||
---
|
||||
|
||||
### Additional Language Support
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Debrief: Failed Frosted Glass Implementation (Attempt 1)
|
||||
|
||||
## 1. Post-Mortem Summary
|
||||
The initial implementation of the Frosted Glass effect was a catastrophic failure resulting in application crashes (`RecursionError`, `AttributeError`, `RuntimeError`) and visual non-functionality (black backgrounds or invisible blurs).
|
||||
|
||||
## 2. Root Causes
|
||||
|
||||
### A. Architectural Blindness (ImGui Timing)
|
||||
I attempted to use `glCopyTexImage2D` to capture the "backbuffer" during the `_gui_func` execution. In an immediate-mode GUI (ImGui), the backbuffer is cleared at the start of the frame and draw commands are only recorded during `_gui_func`. The actual GPU rendering happens **after** `_gui_func` finishes. Consequently, I was capturing and blurring an empty black screen every frame.
|
||||
|
||||
### B. Sub-Agent Fragmentation (Class Scope Breaks)
|
||||
By delegating massive file refactors to the `generalist` sub-agent, I lost control over the strict 1-space indentation required by this project. The sub-agent introduced unindented blocks that silently closed the `App` class scope, causing all subsequent methods to become global functions. This lead to the avalanche of `AttributeError: 'App' object has no attribute '_render_operations_hub_contents'` and similar errors.
|
||||
|
||||
### C. Style Stack Imbalance
|
||||
The implementation of `_begin_window` and `_end_window` wrappers failed to account for mid-render state changes. Toggling the "Frosted Glass" checkbox mid-frame resulted in mismatched `PushStyleColor` and `PopStyleColor` calls, triggering internal ImGui assertions and hard crashes.
|
||||
|
||||
### D. High-DPI Math Errors
|
||||
The UV coordinate math failed to correctly account for `display_framebuffer_scale`. On high-resolution screens, the blur sampling was offset by thousands of pixels, rendering the effect physically invisible or distorted.
|
||||
|
||||
TODO:
|
||||
|
||||
LOOK AT THIS SHIT:
|
||||
https://www.unknowncheats.me/forum/general-programming-and-reversing/617284-blurring-imgui-basically-window-using-acrylic-blur.html
|
||||
https://github.com/Speykious/opengl-playground/blob/main/src/scenes/blurring.rs
|
||||
https://www.intel.com/content/www/us/en/developer/articles/technical/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms.html
|
||||
https://github.com/cofenberg/unrimp/blob/45aa431286ce597c018675c1a9730d98e6ccfc64/Renderer/RendererRuntime/src/DebugGui/DebugGuiManager.cpp
|
||||
https://github.com/cofenberg/unrimp/blob/45aa431286ce597c018675c1a9730d98e6ccfc64/Renderer/RendererRuntime/src/DebugGui/Detail/Shader/DebugGui_GLSL_410.h
|
||||
https://github.com/itsRythem/ImGui-Blur
|
||||
@@ -0,0 +1,6 @@
|
||||
# Track frosted_glass_20260313 Context (REPAIR)
|
||||
|
||||
- [Debrief](./debrief.md)
|
||||
- [Specification](./spec.md)
|
||||
- [Implementation Plan](./plan.md)
|
||||
- [Metadata](./metadata.json)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"track_id": "frosted_glass_20260313",
|
||||
"type": "feature",
|
||||
"status": "new",
|
||||
"created_at": "2026-03-13T14:39:00Z",
|
||||
"updated_at": "2026-03-13T18:55:00Z",
|
||||
"description": "REPAIR: Implement stable frosted glass using native Windows DWM APIs."
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# Implementation Plan: Frosted Glass Background Effect (REPAIR - TRUE GPU)
|
||||
|
||||
## Phase 1: Robust Shader & FBO Foundation
|
||||
- [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
|
||||
- [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
|
||||
- [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.
|
||||
@@ -0,0 +1,30 @@
|
||||
# Specification: Frosted Glass Background Effect (REPAIR - TRUE GPU)
|
||||
|
||||
## Overview
|
||||
Implement a high-fidelity "frosted glass" (acrylic) background effect using a dedicated OpenGL pipeline. This implementation follows professional rendering patterns (downsampling, multi-pass blurring, and screen-space sampling) to ensure a smooth, milky look that remains performant on high-DPI displays.
|
||||
|
||||
## Functional Requirements
|
||||
- **Dedicated Background Pipeline:**
|
||||
- Render the animated "Deep Sea" background shader to an off-screen `SceneFBO` once per frame.
|
||||
- **Multi-Scale Downsampled Blur:**
|
||||
- Downsample the `SceneFBO` texture to 1/4 or 1/8 resolution.
|
||||
- Perform 2-pass Gaussian blurring on the downsampled texture to achieve a creamy "milky" aesthetic.
|
||||
- **ImGui Panel Integration:**
|
||||
- Each ImGui panel must sample its background from the blurred texture using screen-space UV coordinates.
|
||||
- Automatically force window transparency (`alpha 0.0`) when the effect is active.
|
||||
- **Real-Time Shader Tuning:**
|
||||
- Control blur radius, tint intensity, and opacity via the Live Shader Editor.
|
||||
- **Stability:**
|
||||
- Balanced style-stack management to prevent ImGui assertion crashes.
|
||||
- Strict 1-space indentation and class scope protection.
|
||||
|
||||
## Technical Implementation
|
||||
- **FBO Management:** Persistent FBOs for scene, temp, and blur textures.
|
||||
- **UV Math:** `(window_pos / screen_res)` mapping to handle high-DPI scaling and vertical flipping.
|
||||
- **DrawList Callbacks:** (If necessary) use callbacks to ensure the background is ready before panels draw.
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] Toggling the effect does not crash the app.
|
||||
- [ ] Windows show a deep, high-quality blur of the background shader.
|
||||
- [ ] Blur follows windows perfectly during drag/resize.
|
||||
- [ ] The "Milky" look is highly visible even at low radii.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Track text_viewer_rich_rendering_20260313 Context
|
||||
|
||||
- [Specification](./spec.md)
|
||||
- [Implementation Plan](./plan.md)
|
||||
- [Metadata](./metadata.json)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"track_id": "text_viewer_rich_rendering_20260313",
|
||||
"type": "feature",
|
||||
"status": "new",
|
||||
"created_at": "2026-03-13T14:22:00Z",
|
||||
"updated_at": "2026-03-13T14:22:00Z",
|
||||
"description": "Make the text viewer support syntax highlighting and markdown for different text types. Whatever feeds the text viewer new context must specify the type to use otherwise fallback to just regular text visualization without highlighting or markdown rendering."
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# Implementation Plan: Advanced Text Viewer with Syntax Highlighting
|
||||
|
||||
## Phase 1: State & Interface Update
|
||||
- [ ] Task: Audit `src/gui_2.py` to ensure all `text_viewer_*` state variables are explicitly initialized in `App.__init__`.
|
||||
- [ ] Task: Implement: Update `App.__init__` to initialize `self.show_text_viewer`, `self.text_viewer_title`, `self.text_viewer_content`, and new `self.text_viewer_type` (defaulting to "text").
|
||||
- [ ] Task: Implement: Update `self.text_viewer_wrap` (defaulting to True) to allow independent word wrap.
|
||||
- [ ] Task: Implement: Update `_render_text_viewer(self, label: str, content: str, text_type: str = "text")` signature and caller usage.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 1: State & Interface Update' (Protocol in workflow.md)
|
||||
|
||||
## Phase 2: Core Rendering Logic (Code & MD)
|
||||
- [ ] Task: Write Tests: Create a simulation test in `tests/test_gui_text_viewer.py` to verify the viewer opens and switches rendering paths based on `text_type`.
|
||||
- [ ] Task: Implement: In `src/gui_2.py`, refactor the text viewer window loop to:
|
||||
- Use `MarkdownRenderer.render` if `text_type == "markdown"`.
|
||||
- Use a cached `ImGuiColorTextEdit.TextEditor` if `text_type` matches a code language.
|
||||
- Fallback to `imgui.input_text_multiline` for plain text.
|
||||
- [ ] Task: Implement: Ensure the `TextEditor` instance is properly cached using a unique key for the text viewer to maintain state.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Core Rendering Logic' (Protocol in workflow.md)
|
||||
|
||||
## Phase 3: UI Features (Copy, Line Numbers, Wrap)
|
||||
- [ ] Task: Write Tests: Update `tests/test_gui_text_viewer.py` to verify the copy-to-clipboard functionality and word wrap toggle.
|
||||
- [ ] Task: Implement: Add a "Copy" button to the text viewer title bar or a small toolbar at the top of the window.
|
||||
- [ ] Task: Implement: Add a "Word Wrap" checkbox inside the text viewer window.
|
||||
- [ ] Task: Implement: Configure the `TextEditor` instance to show line numbers and be read-only.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 3: UI Features' (Protocol in workflow.md)
|
||||
|
||||
## Phase 4: Integration & Rollout
|
||||
- [ ] Task: Implement: Update all existing calls to `_render_text_viewer` in `src/gui_2.py` (e.g., in `_render_files_panel`, `_render_tool_calls_panel`) to pass the correct `text_type` based on file extension or content.
|
||||
- [ ] Task: Implement: Add "Markdown Preview" support for system prompt presets using the new text viewer logic.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Integration & Rollout' (Protocol in workflow.md)
|
||||
@@ -0,0 +1,30 @@
|
||||
# Specification: Advanced Text Viewer with Syntax Highlighting
|
||||
|
||||
## Overview
|
||||
Enhance the existing "Text Viewer" popup panel in the Manual Slop GUI to support rich rendering, including syntax highlighting for various code types and Markdown rendering. The viewer will transition from a basic text/multiline input to a specialized component leveraging the project's hybrid rendering pattern.
|
||||
|
||||
## Functional Requirements
|
||||
- **Rich Rendering Support:**
|
||||
- **Code:** Integration with `ImGuiColorTextEdit` for syntax highlighting (Python, PowerShell, JSON, TOML, etc.).
|
||||
- **Markdown:** Integration with `imgui_markdown` for rendering formatted text and documents.
|
||||
- **Fallback:** Plain text rendering for unknown or unspecified types.
|
||||
- **Explicit Type Specification:**
|
||||
- The component/function triggering the viewer (e.g., `_render_text_viewer`) must provide an explicit `text_type` parameter (e.g., "python", "markdown", "text").
|
||||
- **Enhanced UI Features:**
|
||||
- **Line Numbers:** Display line numbers in the gutter when viewing code.
|
||||
- **Copy Button:** A dedicated button to copy the entire content to the clipboard.
|
||||
- **Independent Word Wrap:** A toggle within the viewer window to enable/disable word wrapping specifically for that instance, overriding the global GUI setting if necessary.
|
||||
- **Persistent Sizing:** The viewer should maintain its size/position via ImGui's standard `.ini` persistence.
|
||||
|
||||
## Technical Implementation
|
||||
- Update `App` state in `src/gui_2.py` to store `text_viewer_type`.
|
||||
- Modify `_render_text_viewer` signature to accept `text_type`.
|
||||
- Update the rendering loop in `_gui_func` to switch between `MarkdownRenderer` logic and `TextEditor` logic based on `text_viewer_type`.
|
||||
- Ensure proper caching of `TextEditor` instances to maintain scroll position and selection state while the viewer is open.
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] Clicking a preview button for a Python file opens the viewer with syntax highlighting and line numbers.
|
||||
- [ ] Clicking a preview for a `.md` file renders it as formatted Markdown.
|
||||
- [ ] The "Copy" button correctly copies text to the OS clipboard.
|
||||
- [ ] The word wrap toggle works immediately without affecting other panels.
|
||||
- [ ] Unsupported types gracefully fall back to standard plain text.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Track thinking_trace_handling_20260313 Context
|
||||
|
||||
- [Specification](./spec.md)
|
||||
- [Implementation Plan](./plan.md)
|
||||
- [Metadata](./metadata.json)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"track_id": "thinking_trace_handling_20260313",
|
||||
"type": "feature",
|
||||
"status": "new",
|
||||
"created_at": "2026-03-13T13:28:00Z",
|
||||
"updated_at": "2026-03-13T13:28:00Z",
|
||||
"description": "Properly section and handle 'agent thinking' responses from the ai. Right now we just have <thinking> indicators not sure if thats a bodge or if there is a richer way we could be handling this..."
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
# Implementation Plan: Rich Thinking Trace Handling
|
||||
|
||||
## Phase 1: Core Parsing & Model Update
|
||||
- [ ] Task: Audit `src/models.py` and `src/project_manager.py` to identify current message serialization schemas.
|
||||
- [ ] Task: Write Tests: Verify that raw AI responses with `<thinking>`, `<thought>`, and `Thinking:` markers are correctly parsed into segmented data structures (Thinking vs. Response).
|
||||
- [ ] Task: Implement: Add `ThinkingSegment` model and update `ChatMessage` schema in `src/models.py` to support optional thinking traces.
|
||||
- [ ] Task: Implement: Update parsing logic in `src/ai_client.py` or a dedicated utility to extract segments from raw provider responses.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Core Parsing & Model Update' (Protocol in workflow.md)
|
||||
|
||||
## Phase 2: Persistence & History Integration
|
||||
- [ ] Task: Write Tests: Verify that `ProjectManager` correctly serializes and deserializes messages with thinking segments to/from TOML history files.
|
||||
- [ ] Task: Implement: Update `src/project_manager.py` to handle the new `ChatMessage` schema during session save/load.
|
||||
- [ ] Task: Implement: Ensure `src/aggregate.py` or relevant context builders include thinking traces in the "Discussion History" sent back to the AI.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Persistence & History Integration' (Protocol in workflow.md)
|
||||
|
||||
## Phase 3: GUI Rendering - Comms & Discussion
|
||||
- [ ] Task: Write Tests: Verify the GUI rendering logic correctly handles messages with and without thinking segments.
|
||||
- [ ] Task: Implement: Create a reusable `_render_thinking_trace` helper in `src/gui_2.py` using a collapsible header (e.g., `imgui.collapsing_header`).
|
||||
- [ ] Task: Implement: Integrate the thinking trace renderer into the **Comms History** panel in `src/gui_2.py`.
|
||||
- [ ] Task: Implement: Integrate the thinking trace renderer into the **Discussion Hub** message loop in `src/gui_2.py`.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 3: GUI Rendering - Comms & Discussion' (Protocol in workflow.md)
|
||||
|
||||
## Phase 4: Final Polish & Theming
|
||||
- [ ] Task: Implement: Apply specialized styling (e.g., tinted background or italicized text) to expanded thinking traces to distinguish them from direct responses.
|
||||
- [ ] Task: Implement: Ensure thinking trace headers show a "Calculating..." or "Monologue" indicator while an agent is active.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Final Polish & Theming' (Protocol in workflow.md)
|
||||
@@ -0,0 +1,31 @@
|
||||
# Specification: Rich Thinking Trace Handling
|
||||
|
||||
## Overview
|
||||
Implement a formal system for parsing, storing, and rendering "agent thinking" monologues (chains of thought) within the Manual Slop GUI. Currently, thinking traces are treated as raw text or simple markers. This track will introduce a structured UI pattern to separate internal monologue from direct user responses while preserving both for future context.
|
||||
|
||||
## Functional Requirements
|
||||
- **Multi-Format Parsing:** Support extraction of thinking traces from `<thinking>...</thinking>`, `<thought>...</thought>`, and blocks prefixed with `Thinking:`.
|
||||
- **Integrated UI Rendering:**
|
||||
- In the **Comms History** and **Discussion Hub**, thinking traces must be rendered in a distinct, collapsible section.
|
||||
- The section should be **Collapsed by Default** to minimize visual noise.
|
||||
- Thinking traces must be visually separated from the "visible" response (e.g., using a tinted background, border, or specialized header).
|
||||
- **Persistent State Management:**
|
||||
- Both the thinking monologue and the final response must be saved to the permanent discussion history (`manual_slop_history.toml` or `project_history.toml`).
|
||||
- History entries must be properly tagged/schematized to distinguish between thinking and output.
|
||||
- **Context Recurrence:**
|
||||
- Thinking traces must be included in subsequent AI turns (Full Recurrence) to maintain the model's internal state and logical progression.
|
||||
|
||||
## Non-Functional Requirements
|
||||
- **Performance:** Parsing and rendering of thinking blocks must not introduce visible latency in the GUI thread.
|
||||
- **Accessibility:** All thinking blocks must remain selectable and copyable via the standard high-fidelity selectable UI pattern.
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] AI responses containing `<thinking>` or similar tags are automatically parsed into separate segments.
|
||||
- [ ] A "Thinking..." header appears in the Discussion Hub for messages with monologues.
|
||||
- [ ] Clicking the header expands the full thinking trace.
|
||||
- [ ] Saving/Loading a project preserves the distinction between thinking and response.
|
||||
- [ ] Subsequent AI calls receive the thinking trace as part of the conversation history.
|
||||
|
||||
## Out of Scope
|
||||
- Implementing "Hidden Thinking" (where the user cannot see it but the AI can).
|
||||
- Real-time "Streaming" of thinking into the UI (unless already supported by the active provider).
|
||||
+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