meltygui 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (372) hide show
  1. meltygui/__init__.py +107 -0
  2. meltygui/accounts/__init__.py +0 -0
  3. meltygui/accounts/internet_accounts.py +1355 -0
  4. meltygui/chat/__init__.py +91 -0
  5. meltygui/chat/activity.py +75 -0
  6. meltygui/chat/backends.py +36 -0
  7. meltygui/chat/chat_interface.py +732 -0
  8. meltygui/chat/chat_proxy.py +352 -0
  9. meltygui/chat/codex_proxy.py +592 -0
  10. meltygui/chat/codex_settings.py +100 -0
  11. meltygui/chat/codex_transport.py +60 -0
  12. meltygui/chat/command_parser.py +204 -0
  13. meltygui/chat/images.py +227 -0
  14. meltygui/chat/messages.py +417 -0
  15. meltygui/chat/metadata.py +139 -0
  16. meltygui/chat/writer_locks.py +64 -0
  17. meltygui/code/__init__.py +0 -0
  18. meltygui/code/basic_converters.py +533 -0
  19. meltygui/code/chain_converters.py +2111 -0
  20. meltygui/code/code_checks.py +2209 -0
  21. meltygui/code/core_syntax.py +1430 -0
  22. meltygui/code/file_converters.py +1933 -0
  23. meltygui/code/fileref.py +702 -0
  24. meltygui/code/hotswap_guard.py +144 -0
  25. meltygui/code/libcst_conversion.py +9724 -0
  26. meltygui/code/live_instrument.py +392 -0
  27. meltygui/code/live_view.py +2490 -0
  28. meltygui/code/melty_scan.py +2684 -0
  29. meltygui/code/new_codecs.py +1255 -0
  30. meltygui/code/new_converters.py +3017 -0
  31. meltygui/code/project_code.py +278 -0
  32. meltygui/code/source_context.py +63 -0
  33. meltygui/code/symbol_roster.py +1588 -0
  34. meltygui/code/syntax_check.py +34 -0
  35. meltygui/code/syntax_check_worker.py +114 -0
  36. meltygui/completion/__init__.py +0 -0
  37. meltygui/completion/fim.py +1232 -0
  38. meltygui/completion/fim_context.py +481 -0
  39. meltygui/completion/providers/__init__.py +0 -0
  40. meltygui/completion/providers/anthropic_oauth.py +446 -0
  41. meltygui/completion/providers/anthropic_requests.py +69 -0
  42. meltygui/completion/providers/claude.py +203 -0
  43. meltygui/completion/providers/claude_usage.py +499 -0
  44. meltygui/completion/providers/codex_accounts.py +180 -0
  45. meltygui/completion/providers/copilot.py +617 -0
  46. meltygui/completion/providers/oauth_popup.py +220 -0
  47. meltygui/completion/providers/ollama.py +320 -0
  48. meltygui/completion/providers/profiles.py +23 -0
  49. meltygui/core/README.md +88 -0
  50. meltygui/core/__init__.py +1 -0
  51. meltygui/core/automation/__init__.py +1 -0
  52. meltygui/core/automation/action_core.py +153 -0
  53. meltygui/core/automation/collection_action.py +45 -0
  54. meltygui/core/automation/mcp_eval.py +167 -0
  55. meltygui/core/automation/mcp_hotswap.py +107 -0
  56. meltygui/core/automation/mcp_query.py +552 -0
  57. meltygui/core/automation/mcp_server.py +657 -0
  58. meltygui/core/automation/orchestration_core.py +2636 -0
  59. meltygui/core/automation/query_core.py +124 -0
  60. meltygui/core/automation/search_core.py +26 -0
  61. meltygui/core/automation/selector_core.py +340 -0
  62. meltygui/core/automation/value_core.py +1752 -0
  63. meltygui/core/cache/__init__.py +1 -0
  64. meltygui/core/cache/cache_diagnostics.py +0 -0
  65. meltygui/core/cache/invalidation_decoration.py +153 -0
  66. meltygui/core/cache/invalidation_tracker.py +43 -0
  67. meltygui/core/cache/tile_cache.py +5968 -0
  68. meltygui/core/conversion/__init__.py +1 -0
  69. meltygui/core/conversion/bubbling.py +599 -0
  70. meltygui/core/conversion/cache_tree.py +188 -0
  71. meltygui/core/conversion/chain.py +113 -0
  72. meltygui/core/conversion/converter_register.py +145 -0
  73. meltygui/core/conversion/data_decoration.py +67 -0
  74. meltygui/core/conversion/dict_conversion.py +1815 -0
  75. meltygui/core/conversion/dict_conversion_util.py +177 -0
  76. meltygui/core/conversion/dynamic_obj.py +89 -0
  77. meltygui/core/conversion/graph_compare.py +183 -0
  78. meltygui/core/conversion/load_save_v2.py +1032 -0
  79. meltygui/core/conversion/missing_saved_class.py +39 -0
  80. meltygui/core/conversion/path_finder.py +606 -0
  81. meltygui/core/conversion/render_host.py +1083 -0
  82. meltygui/core/core_render.py +6537 -0
  83. meltygui/core/definition_hotswap.py +231 -0
  84. meltygui/core/diagnostics/__init__.py +1 -0
  85. meltygui/core/diagnostics/attribute_churn.py +38 -0
  86. meltygui/core/diagnostics/fps_counter.py +39 -0
  87. meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
  88. meltygui/core/diagnostics/inspection_core.py +169 -0
  89. meltygui/core/diagnostics/monitor_core.py +105 -0
  90. meltygui/core/diagnostics/notifications.py +706 -0
  91. meltygui/core/diagnostics/perf_trace.py +281 -0
  92. meltygui/core/diagnostics/profile_decoration.py +88 -0
  93. meltygui/core/diagnostics/resize_trace.py +62 -0
  94. meltygui/core/diagnostics/screenshot_core.py +247 -0
  95. meltygui/core/diagnostics/session_status.py +98 -0
  96. meltygui/core/diagnostics/trace_core.py +445 -0
  97. meltygui/core/files/__init__.py +1 -0
  98. meltygui/core/files/file_core.py +208 -0
  99. meltygui/core/files/file_explorer_core.py +104 -0
  100. meltygui/core/files/file_tree_core.py +198 -0
  101. meltygui/core/files/file_watch_core.py +43 -0
  102. meltygui/core/files/import_graph_core.py +43 -0
  103. meltygui/core/files/metadata_core.py +51 -0
  104. meltygui/core/graphics/__init__.py +1 -0
  105. meltygui/core/graphics/cuda_context_core.py +166 -0
  106. meltygui/core/graphics/cuda_interop_core.py +136 -0
  107. meltygui/core/graphics/cuda_kernel_core.py +91 -0
  108. meltygui/core/graphics/framebuffer_recorder.py +337 -0
  109. meltygui/core/graphics/gl_state.py +658 -0
  110. meltygui/core/graphics/lut_core.py +52 -0
  111. meltygui/core/graphics/overlay_renderer.py +984 -0
  112. meltygui/core/graphics/scene_target.py +180 -0
  113. meltygui/core/graphics/screenshot.py +439 -0
  114. meltygui/core/graphics/shader_func.py +478 -0
  115. meltygui/core/graphics/tensor_core.py +45 -0
  116. meltygui/core/graphics/text_texture.py +329 -0
  117. meltygui/core/graphics/wayland_color.py +635 -0
  118. meltygui/core/input/__init__.py +1 -0
  119. meltygui/core/input/collision.py +165 -0
  120. meltygui/core/input/drag_drop_core.py +1525 -0
  121. meltygui/core/input/hypr_left_drag.py +323 -0
  122. meltygui/core/input/input_core.py +245 -0
  123. meltygui/core/input/input_handler.py +1101 -0
  124. meltygui/core/input/mouse_cursor.py +355 -0
  125. meltygui/core/input/pynput_backend.py +1054 -0
  126. meltygui/core/input/space_mouse.py +338 -0
  127. meltygui/core/input/touchpad_backend.py +393 -0
  128. meltygui/core/input/view_selection.py +177 -0
  129. meltygui/core/layout/__init__.py +1 -0
  130. meltygui/core/layout/column_core.py +2153 -0
  131. meltygui/core/layout/cursor_core.py +161 -0
  132. meltygui/core/layout/dropdown_core.py +278 -0
  133. meltygui/core/layout/edge_constraints.py +155 -0
  134. meltygui/core/layout/grid_core.py +117 -0
  135. meltygui/core/layout/header_core.py +23 -0
  136. meltygui/core/layout/header_runtime.py +67 -0
  137. meltygui/core/layout/layout_core.py +87 -0
  138. meltygui/core/layout/tile_manager_core.py +591 -0
  139. meltygui/core/melty.py +6726 -0
  140. meltygui/core/module_map.json +896 -0
  141. meltygui/core/module_names.py +19 -0
  142. meltygui/core/rendering/__init__.py +1 -0
  143. meltygui/core/rendering/core_decoration.py +431 -0
  144. meltygui/core/rendering/core_render_helpers.py +328 -0
  145. meltygui/core/rendering/func_metadata.py +398 -0
  146. meltygui/core/rendering/mode.py +818 -0
  147. meltygui/core/rendering/mode_defaults.py +41 -0
  148. meltygui/core/rendering/modes.py +136 -0
  149. meltygui/core/rendering/parameter_core.py +1665 -0
  150. meltygui/core/rendering/render_dispatch.py +1891 -0
  151. meltygui/core/rendering/render_funcs.py +273 -0
  152. meltygui/core/rendering/shaped.py +312 -0
  153. meltygui/core/rendering/window_decoration.py +25 -0
  154. meltygui/core/runtime/__init__.py +1 -0
  155. meltygui/core/runtime/app.py +735 -0
  156. meltygui/core/runtime/app_session.py +140 -0
  157. meltygui/core/runtime/background.py +564 -0
  158. meltygui/core/runtime/extensions.py +53 -0
  159. meltygui/core/runtime/gc_manager.py +1163 -0
  160. meltygui/core/runtime/lifecycle.py +21 -0
  161. meltygui/core/runtime/paths.py +27 -0
  162. meltygui/core/runtime/settings.py +14 -0
  163. meltygui/core/runtime/singleton.py +16 -0
  164. meltygui/core/runtime/thread_safe_bool.py +24 -0
  165. meltygui/core/runtime/thread_signal.py +30 -0
  166. meltygui/core/runtime/toggles.py +3115 -0
  167. meltygui/core/services/__init__.py +1 -0
  168. meltygui/core/services/account_core.py +10 -0
  169. meltygui/core/services/chat_core.py +19 -0
  170. meltygui/core/services/claude_terminal_core.py +346 -0
  171. meltygui/core/services/terminal_core.py +458 -0
  172. meltygui/core/services/terminal_runtime.py +78 -0
  173. meltygui/core/styling/__init__.py +1 -0
  174. meltygui/core/styling/color_core.py +46 -0
  175. meltygui/core/styling/fonts.py +639 -0
  176. meltygui/core/styling/global_style.py +338 -0
  177. meltygui/core/styling/style.py +198 -0
  178. meltygui/core/styling/style_core.py +522 -0
  179. meltygui/core/styling/warm_start.py +149 -0
  180. meltygui/core/windowing/__init__.py +1 -0
  181. meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
  182. meltygui/core/windowing/backends/__init__.py +1 -0
  183. meltygui/core/windowing/backends/imgui_renderer.py +138 -0
  184. meltygui/core/windowing/backends/native_wayland.py +850 -0
  185. meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
  186. meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
  187. meltygui/core/windowing/backends/wayland_protocol.py +101 -0
  188. meltygui/core/windowing/dock_core.py +182 -0
  189. meltygui/core/windowing/frame_geometry.py +42 -0
  190. meltygui/core/windowing/geometry_feed.py +864 -0
  191. meltygui/core/windowing/glfw_utils.py +1343 -0
  192. meltygui/core/windowing/os_frame.py +1552 -0
  193. meltygui/core/windowing/surface.py +643 -0
  194. meltygui/core/windowing/titlebar.py +1560 -0
  195. meltygui/core/windowing/titlebar_buttons.py +281 -0
  196. meltygui/core/windowing/wayland_move.py +932 -0
  197. meltygui/core/windowing/window_api.py +62 -0
  198. meltygui/core/windowing/window_constants.py +339 -0
  199. meltygui/core/windowing/window_visibility.py +162 -0
  200. meltygui/debug/__init__.py +0 -0
  201. meltygui/debug/app_view_utils.py +9 -0
  202. meltygui/editor/__init__.py +0 -0
  203. meltygui/editor/bash_syntax.py +30 -0
  204. meltygui/editor/code_line_fast.py +152 -0
  205. meltygui/editor/diff.py +139 -0
  206. meltygui/editor/external_changes.py +159 -0
  207. meltygui/editor/file_header.py +41 -0
  208. meltygui/editor/live_usage.py +169 -0
  209. meltygui/editor/live_view_views.py +1803 -0
  210. meltygui/editor/pending_save.py +1351 -0
  211. meltygui/editor/roster_tints.py +547 -0
  212. meltygui/editor/source_preview.py +16 -0
  213. meltygui/editor/source_tools.py +10 -0
  214. meltygui/editor/source_ui.py +70 -0
  215. meltygui/editor/spell_check.py +77 -0
  216. meltygui/editor/text_editor.py +9076 -0
  217. meltygui/editor/usage_picker.py +538 -0
  218. meltygui/events/__init__.py +0 -0
  219. meltygui/events/example.py +118 -0
  220. meltygui/examples/__init__.py +0 -0
  221. meltygui/examples/columns_demo.py +33 -0
  222. meltygui/examples/columns_window_demo.py +60 -0
  223. meltygui/examples/context_menu_demo.py +24 -0
  224. meltygui/examples/context_menu_window_demo.py +49 -0
  225. meltygui/examples/gui_playground.py +127 -0
  226. meltygui/examples/live_view_playground.py +256 -0
  227. meltygui/examples/lora.py +43 -0
  228. meltygui/examples/lora_data.py +59 -0
  229. meltygui/examples/lora_policies.py +67 -0
  230. meltygui/examples/mode_demo.py +49 -0
  231. meltygui/examples/modifies_demo.py +119 -0
  232. meltygui/examples/scalar_policies.py +60 -0
  233. meltygui/examples/style_layouts.py +149 -0
  234. meltygui/examples/tile_manager_demo.py +74 -0
  235. meltygui/examples/tint_demo.py +160 -0
  236. meltygui/examples/tint_functions.py +56 -0
  237. meltygui/examples/trace_demo.py +88 -0
  238. meltygui/examples/two_windows.py +36 -0
  239. meltygui/files/__init__.py +0 -0
  240. meltygui/files/fast_file_explorer.py +439 -0
  241. meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
  242. meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
  243. meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
  244. meltygui/graphics/__init__.py +6 -0
  245. meltygui/graphics/base.py +85 -0
  246. meltygui/graphics/examples.py +507 -0
  247. meltygui/graphics/executor.py +520 -0
  248. meltygui/graphics/filter.py +667 -0
  249. meltygui/graphics/filter.pyi +856 -0
  250. meltygui/graphics/generate_stubs.py +22 -0
  251. meltygui/graphics/registry.py +203 -0
  252. meltygui/graphics/shader_compiler.py +155 -0
  253. meltygui/graphics/shaders.py +1302 -0
  254. meltygui/graphics/stub_generator.py +250 -0
  255. meltygui/graphics/texture_manager.py +170 -0
  256. meltygui/graphics/texture_min_max.py +295 -0
  257. meltygui/hdr_color.py +757 -0
  258. meltygui/image_load.py +308 -0
  259. meltygui/model/__init__.py +1 -0
  260. meltygui/model/account_model.py +142 -0
  261. meltygui/model/camera_model.py +159 -0
  262. meltygui/model/chat_model.py +86 -0
  263. meltygui/model/code_model.py +62 -0
  264. meltygui/model/code_proxy_model.py +876 -0
  265. meltygui/model/collection_model.py +41 -0
  266. meltygui/model/color_model.py +127 -0
  267. meltygui/model/cuda_tensor_model.py +36 -0
  268. meltygui/model/cuda_texture_model.py +149 -0
  269. meltygui/model/dropdown_model.py +137 -0
  270. meltygui/model/file_metadata_model.py +73 -0
  271. meltygui/model/file_model.py +289 -0
  272. meltygui/model/format_model.py +390 -0
  273. meltygui/model/graph_model.py +98 -0
  274. meltygui/model/icon_model.py +1024 -0
  275. meltygui/model/import_graph_model.py +468 -0
  276. meltygui/model/layout_model.py +15 -0
  277. meltygui/model/lut_model.py +266 -0
  278. meltygui/model/search_model.py +173 -0
  279. meltygui/model/tensor_model.py +402 -0
  280. meltygui/model/terminal_model.py +329 -0
  281. meltygui/model/texture_model.py +146 -0
  282. meltygui/model/tile_model.py +24 -0
  283. meltygui/model/trace_model.py +198 -0
  284. meltygui/model/trace_report_model.py +146 -0
  285. meltygui/models/__init__.py +0 -0
  286. meltygui/models/file_meta.py +613 -0
  287. meltygui/models/function_console.py +184 -0
  288. meltygui/models/orchestration.py +48 -0
  289. meltygui/pbr.py +1576 -0
  290. meltygui/png_unfilter.c +49 -0
  291. meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
  292. meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
  293. meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
  294. meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  295. meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
  296. meltygui/resources/dejavu/LICENSE.txt +78 -0
  297. meltygui/resources/fontawesome-LICENSE.txt +121 -0
  298. meltygui/resources/fontawesome-webfont.ttf +0 -0
  299. meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
  300. meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
  301. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
  302. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
  303. meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
  304. meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
  305. meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
  306. meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
  307. meltygui/resources/jetbrains-weights/OFL.txt +93 -0
  308. meltygui/resources/jetbrains-weights/README.md +2 -0
  309. meltygui/state/__init__.py +0 -0
  310. meltygui/state/account_state.py +22 -0
  311. meltygui/state/animation_state.py +97 -0
  312. meltygui/state/annotation_state.py +22 -0
  313. meltygui/state/chat_state.py +36 -0
  314. meltygui/state/code_state.py +10 -0
  315. meltygui/state/core_enums.py +59 -0
  316. meltygui/state/core_markers.py +115 -0
  317. meltygui/state/core_undo.py +1075 -0
  318. meltygui/state/file_state.py +75 -0
  319. meltygui/state/graph_state.py +26 -0
  320. meltygui/state/inspection_state.py +47 -0
  321. meltygui/state/menu_state.py +10 -0
  322. meltygui/state/model_enums.py +11 -0
  323. meltygui/state/new_core_model.py +2394 -0
  324. meltygui/state/orchestration_state.py +14 -0
  325. meltygui/state/query_state.py +24 -0
  326. meltygui/state/tensor_state.py +10 -0
  327. meltygui/state/terminal_state.py +23 -0
  328. meltygui/state/trace_state.py +32 -0
  329. meltygui/state/voxel_state.py +12 -0
  330. meltygui/text_index.py +816 -0
  331. meltygui/utils/__init__.py +0 -0
  332. meltygui/utils/jump_to_code.py +344 -0
  333. meltygui/utils/pkl_inspect.py +90 -0
  334. meltygui/utils/render_utils.py +1419 -0
  335. meltygui/view/__init__.py +1 -0
  336. meltygui/view/account_view.py +524 -0
  337. meltygui/view/action_view.py +73 -0
  338. meltygui/view/chat_decoration_view.py +112 -0
  339. meltygui/view/chat_view.py +1703 -0
  340. meltygui/view/code_view.py +2677 -0
  341. meltygui/view/collection_view.py +1185 -0
  342. meltygui/view/color_view.py +824 -0
  343. meltygui/view/control_view.py +559 -0
  344. meltygui/view/decoration_view.py +439 -0
  345. meltygui/view/diagnostic_view.py +177 -0
  346. meltygui/view/dropdown_view.py +1082 -0
  347. meltygui/view/file_view.py +1599 -0
  348. meltygui/view/graph_cuda_view.py +105 -0
  349. meltygui/view/graph_view.py +882 -0
  350. meltygui/view/header_view.py +848 -0
  351. meltygui/view/input_view.py +238 -0
  352. meltygui/view/inspection_view.py +1783 -0
  353. meltygui/view/layout_view.py +596 -0
  354. meltygui/view/lut_view.py +45 -0
  355. meltygui/view/menu_view.py +212 -0
  356. meltygui/view/orchestration_view.py +658 -0
  357. meltygui/view/query_view.py +118 -0
  358. meltygui/view/search_view.py +342 -0
  359. meltygui/view/tab_view.py +258 -0
  360. meltygui/view/tensor_view.py +430 -0
  361. meltygui/view/terminal_view.py +355 -0
  362. meltygui/view/text_view.py +8031 -0
  363. meltygui/view/texture_view.py +480 -0
  364. meltygui/view/tile_view.py +38 -0
  365. meltygui/view/trace_view.py +923 -0
  366. meltygui/view/voxel_cuda_view.py +824 -0
  367. meltygui/view/voxel_view.py +1860 -0
  368. meltygui/view/window_view.py +111 -0
  369. meltygui-0.1.0.dist-info/METADATA +143 -0
  370. meltygui-0.1.0.dist-info/RECORD +372 -0
  371. meltygui-0.1.0.dist-info/WHEEL +4 -0
  372. meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,667 @@
1
+ """
2
+ Filter - main interface for shader-based texture filtering.
3
+ """
4
+
5
+ from typing import Dict, Any, Optional, List, Tuple
6
+ from contextlib import contextmanager
7
+ from functools import partial
8
+
9
+ import numpy as np
10
+
11
+ from meltygui.graphics.shader_compiler import ProgramCompiler
12
+ from meltygui.graphics.shader_compiler import CompiledProgram
13
+ from meltygui.graphics.shader_compiler import ShaderCompilationError
14
+ from meltygui.graphics.executor import FilterExecutor
15
+ from meltygui.graphics.registry import get_registry
16
+
17
+ import meltygui.graphics.texture_min_max as texture_min_max
18
+
19
+
20
+ class FilterChain:
21
+ """
22
+ A chainable sequence of filters.
23
+
24
+ Usage:
25
+ chain = filter.chain()
26
+ result = (chain
27
+ .brightness_contrast(brightness=0.2)
28
+ .gaussian_blur(radius=3.0)
29
+ .apply(texture_id))
30
+ """
31
+
32
+ def __init__(self, filter_instance: 'Filter'):
33
+ """
34
+ Initialize a filter chain.
35
+
36
+ Args:
37
+ filter_instance: The parent Filter instance
38
+ """
39
+ self._filter = filter_instance
40
+ self._steps: list = []
41
+
42
+ def __getattr__(self, name: str):
43
+ """Add a filter step to the chain."""
44
+ if name.startswith('_'):
45
+ raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
46
+
47
+ if not self._filter.has_shader(name):
48
+ available = ', '.join(self._filter.list_shaders())
49
+ raise AttributeError(
50
+ f"No shader named '{name}'. Available shaders: {available}"
51
+ )
52
+
53
+ def add_step(**uniforms):
54
+ self._steps.append((name, uniforms))
55
+ return self
56
+
57
+ return add_step
58
+
59
+ def apply(self, texture_id: int, in_place: bool = False) -> int:
60
+ """
61
+ Apply the filter chain to a texture.
62
+
63
+ Args:
64
+ texture_id: Input texture ID
65
+ in_place: If True, modify the input texture (final output)
66
+
67
+ Returns:
68
+ The output texture ID
69
+ """
70
+ if not self._steps:
71
+ return texture_id
72
+
73
+ current_texture = texture_id
74
+
75
+ for i, (shader_name, uniforms) in enumerate(self._steps):
76
+ is_last = (i == len(self._steps) - 1)
77
+
78
+ if is_last and in_place:
79
+ # Last step - render to original texture
80
+ current_texture = self._filter.apply(
81
+ shader_name, current_texture,
82
+ in_place=True, **uniforms
83
+ )
84
+ else:
85
+ # Intermediate step - render to new texture
86
+ current_texture = self._filter.apply(
87
+ shader_name, current_texture,
88
+ in_place=False, **uniforms
89
+ )
90
+
91
+ return current_texture
92
+
93
+ def clear(self) -> 'FilterChain':
94
+ """Clear all steps from the chain."""
95
+ self._steps.clear()
96
+ return self
97
+
98
+ def __len__(self) -> int:
99
+ """Get the number of steps in the chain."""
100
+ return len(self._steps)
101
+
102
+ def __repr__(self) -> str:
103
+ steps = ' -> '.join(name for name, _ in self._steps)
104
+ return f"FilterChain([{steps}])"
105
+
106
+
107
+ class Filter:
108
+ """
109
+ Main interface for shader-based texture filtering.
110
+
111
+ Provides attribute-style access to shader filters and manages
112
+ shader lifecycle, compilation, and execution.
113
+
114
+ Usage:
115
+ filter = Filter()
116
+
117
+ # Apply filters using attribute access
118
+ texture_id = filter.brightness_contrast(texture_id, brightness=0.5)
119
+ texture_id = filter.vignette(texture_id, in_place=True)
120
+
121
+ # Chain multiple filters
122
+ result = (filter.chain()
123
+ .brightness_contrast(brightness=0.2)
124
+ .gaussian_blur_h(radius=3.0)
125
+ .apply(texture_id))
126
+
127
+ # Clean up when done
128
+ filter.cleanup()
129
+ """
130
+
131
+ def __init__(self, auto_compile: bool = True):
132
+ """
133
+ Initialize the Filter manager.
134
+
135
+ Args:
136
+ auto_compile: If True, compile shaders on first use.
137
+ """
138
+ self._auto_compile = auto_compile
139
+ self._registry = get_registry()
140
+ self._compiler = ProgramCompiler()
141
+ self._executor = FilterExecutor(self._compiler)
142
+ self._compiled: Dict[str, CompiledProgram] = {}
143
+
144
+ # Dynamically generate methods for all registered shaders
145
+ # This enables IDE autocomplete and type hints
146
+ self._generate_shader_methods()
147
+
148
+ def _generate_shader_methods(self) -> None:
149
+ """
150
+ Dynamically generate methods for all registered shaders.
151
+
152
+ This creates actual methods on the instance for each shader,
153
+ enabling IDE autocomplete and type hints.
154
+ """
155
+ for shader_name in self._registry.shaders.keys():
156
+ # Create a closure to capture shader_name
157
+ def make_method(name: str):
158
+ def shader_method(
159
+ texture_id: int = 0,
160
+ in_place: bool = False,
161
+ output_texture: Optional[int] = None,
162
+ output_framebuffer: Optional[int] = None,
163
+ input_framebuffer: Optional[int] = None,
164
+ output_size: Optional[Tuple[int, int]] = None,
165
+ **uniforms
166
+ ) -> int:
167
+ """
168
+ Apply this shader filter to a texture or framebuffer.
169
+
170
+ Args:
171
+ texture_id: Input texture ID (ignored if input_framebuffer is set)
172
+ in_place: If True, modify the input texture directly
173
+ output_texture: Optional specific output texture to render to
174
+ output_framebuffer: Optional framebuffer to render to (e.g., 0 for main screen)
175
+ input_framebuffer: Optional framebuffer to read from (e.g., 0 for main screen)
176
+ output_size: Optional (width, height) render resolution. Runs the
177
+ shader at a lower resolution than the input while still sampling
178
+ the full-res input - the resulting smaller texture upscales
179
+ automatically when sampled downstream.
180
+ **uniforms: Uniform values to pass to the shader
181
+
182
+ Returns:
183
+ The output texture ID (same as input if in_place=True, 0 if output_framebuffer)
184
+ """
185
+ return self.apply(name, texture_id, in_place, output_texture, output_framebuffer, input_framebuffer, output_size=output_size, **uniforms)
186
+
187
+ # Set the method name for better debugging
188
+ shader_method.__name__ = name
189
+ return shader_method
190
+
191
+ # Attach the method to this instance
192
+ setattr(self, shader_name, make_method(shader_name))
193
+
194
+ def __getattr__(self, name: str):
195
+ """
196
+ Fallback attribute access for dynamically registered shaders.
197
+
198
+ Note: Most shaders have real methods generated at __init__ time.
199
+ This fallback handles edge cases like shaders registered after init.
200
+ """
201
+ # Don't intercept private attributes
202
+ if name.startswith('_'):
203
+ raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
204
+
205
+ # Check if this shader exists
206
+ if not self.has_shader(name):
207
+ available = ', '.join(self.list_shaders())
208
+ raise AttributeError(
209
+ f"No shader named '{name}'. Available shaders: {available}"
210
+ )
211
+
212
+ # Return a callable that applies this filter
213
+ return partial(self.apply, name)
214
+
215
+ def apply(self, shader_name: str, texture_id: int = 0,
216
+ in_place: bool = False,
217
+ output_texture: Optional[int] = None,
218
+ output_framebuffer: Optional[int] = None,
219
+ input_framebuffer: Optional[int] = None,
220
+ output_size: Optional[Tuple[int, int]] = None,
221
+ **uniforms) -> int:
222
+ """
223
+ Apply a shader filter to a texture or framebuffer.
224
+
225
+ Args:
226
+ shader_name: Name of the shader to apply
227
+ texture_id: Input texture ID (ignored if input_framebuffer is set)
228
+ in_place: If True, modify the input texture directly
229
+ output_texture: Optional specific output texture to render to
230
+ output_framebuffer: Optional framebuffer to render to (e.g., 0 for main screen).
231
+ If set, renders directly to framebuffer instead of creating output texture.
232
+ input_framebuffer: Optional framebuffer to read from (e.g., 0 for main screen).
233
+ If set, reads from this framebuffer instead of texture_id.
234
+ **uniforms: Uniform values to pass to the shader
235
+
236
+ Returns:
237
+ The output texture ID (same as input if in_place=True, 0 if output_framebuffer)
238
+ """
239
+ program = self._get_compiled(shader_name)
240
+ if program is None:
241
+ raise KeyError(f"Shader '{shader_name}' not found or failed to compile")
242
+
243
+ # Fill in default uniform values
244
+ full_uniforms = {}
245
+ for uname, (_, default) in program.shader.uniforms.items():
246
+ if default is not None:
247
+ full_uniforms[uname] = default
248
+ full_uniforms.update(uniforms)
249
+
250
+ return self._executor.execute(
251
+ program, texture_id, full_uniforms,
252
+ in_place=in_place, output_texture=output_texture,
253
+ output_framebuffer=output_framebuffer,
254
+ input_framebuffer=input_framebuffer,
255
+ output_size=output_size
256
+ )
257
+
258
+ def chain(self) -> FilterChain:
259
+ """Create a new filter chain."""
260
+ return FilterChain(self)
261
+
262
+ def compile_shader(self, name: str) -> CompiledProgram:
263
+ """
264
+ Compile a shader by name.
265
+
266
+ Args:
267
+ name: The shader name (snake_case)
268
+
269
+ Returns:
270
+ The compiled program
271
+
272
+ Raises:
273
+ KeyError: If the shader is not registered
274
+ ShaderCompilationError: If compilation fails
275
+ """
276
+ shader = self._registry.get_shader(name)
277
+ if shader is None:
278
+ raise KeyError(f"No shader registered with name '{name}'")
279
+
280
+ shader_type = self._registry.get_type(shader.shader_type)
281
+ if shader_type is None:
282
+ raise KeyError(f"No shader type registered with name '{shader.shader_type}'")
283
+
284
+ compiled = self._compiler.compile(shader, shader_type)
285
+ self._compiled[name] = compiled
286
+ return compiled
287
+
288
+ def compile_all(self) -> Dict[str, CompiledProgram]:
289
+ """
290
+ Compile all registered shaders.
291
+
292
+ Returns:
293
+ Dict mapping shader names to compiled programs
294
+ """
295
+ results = {}
296
+ for name in self._registry.shaders.keys():
297
+ try:
298
+ results[name] = self.compile_shader(name)
299
+ except ShaderCompilationError as e:
300
+ print(f"Warning: Failed to compile '{name}': {e}")
301
+ return results
302
+
303
+ def _get_compiled(self, name: str) -> Optional[CompiledProgram]:
304
+ """
305
+ Get a compiled program by name.
306
+
307
+ If auto_compile is enabled and the shader isn't compiled yet,
308
+ it will be compiled automatically.
309
+ """
310
+ if name not in self._compiled and self._auto_compile:
311
+ if self._registry.get_shader(name) is not None:
312
+ return self.compile_shader(name)
313
+ return self._compiled.get(name)
314
+
315
+ def has_shader(self, name: str) -> bool:
316
+ """Check if a shader is registered."""
317
+ return self._registry.get_shader(name) is not None
318
+
319
+ def list_shaders(self) -> List[str]:
320
+ """List all registered shader names."""
321
+ return list(self._registry.shaders.keys())
322
+
323
+ def list_shader_types(self) -> List[str]:
324
+ """List all registered shader type names."""
325
+ return list(self._registry.shader_types.keys())
326
+
327
+ def get_shader_info(self, name: str) -> Optional[Dict[str, Any]]:
328
+ """
329
+ Get information about a registered shader.
330
+
331
+ Args:
332
+ name: The shader name
333
+
334
+ Returns:
335
+ Dict with shader info, or None if not found
336
+ """
337
+ shader = self._registry.get_shader(name)
338
+ if shader is None:
339
+ return None
340
+
341
+ return {
342
+ 'name': shader.name,
343
+ 'shader_type': shader.shader_type,
344
+ 'uniforms': {
345
+ uname: {'type': utype.value, 'default': default}
346
+ for uname, (utype, default) in shader.uniforms.items()
347
+ },
348
+ 'compiled': name in self._compiled
349
+ }
350
+
351
+ @contextmanager
352
+ def use_shader(self, name: str):
353
+ """
354
+ Context manager for using a shader program.
355
+
356
+ Useful for manual rendering when you need more control.
357
+
358
+ Usage:
359
+ with filter.use_shader('brightness_contrast') as program:
360
+ # Set up your own rendering...
361
+ pass
362
+ """
363
+ from OpenGL import GL
364
+
365
+ program = self._get_compiled(name)
366
+ if program is None:
367
+ raise KeyError(f"Shader '{name}' not found or failed to compile")
368
+
369
+ try:
370
+ GL.glUseProgram(program.program_id)
371
+ yield program
372
+ finally:
373
+ GL.glUseProgram(0)
374
+
375
+ def clear_texture_cache(self) -> None:
376
+ """
377
+ Clear all cached output textures.
378
+
379
+ This frees up GPU memory by deleting all textures in the cache.
380
+ Call this periodically if you're concerned about memory usage.
381
+ """
382
+ self._executor.clear_texture_cache()
383
+
384
+ def clear_fbo_cache(self) -> None:
385
+ """
386
+ Clear all cached FBOs and temp textures.
387
+
388
+ This frees up GPU resources by deleting all cached framebuffers
389
+ and their associated temporary textures.
390
+ """
391
+ self._executor.clear_fbo_cache()
392
+
393
+ def normalize(self, texture_id: int, in_place: bool = False,
394
+ output_texture: Optional[int] = None, min_value=None, max_value=None) -> Any:
395
+ """
396
+ Normalize texture values to [0, 1] range by calculating min/max.
397
+
398
+ This uses GPU-based parallel reduction to efficiently find the global
399
+ min and max values without reading all pixels. The texture is progressively
400
+ downsampled until reaching a 1x1 pixel, then that single pixel is read
401
+ to get the min/max values.
402
+
403
+ Args:
404
+ texture_id: Input texture ID
405
+ in_place: If True, modify the input texture directly
406
+ output_texture: Optional specific output texture to render to
407
+
408
+ Returns:
409
+ (output_texture_id, min_value, max_value). For the auto-computed
410
+ path min/max stay on the GPU and are returned as None — callers that
411
+ need the CPU values should call texture_min_max.get_texture_min_max.
412
+ """
413
+ # The remap reads (min, max) from a 1x1 GPU texture instead of float
414
+ # uniforms, so the range never round-trips through the CPU (the old
415
+ # get_texture_min_max readback was a per-frame GPU sync stall). Both
416
+ # paths below leave the value in that texture, which normalize_remap
417
+ # samples.
418
+ if min_value is not None and max_value is not None:
419
+ # Explicit range: tiny upload, no reduction.
420
+ min_max_tex = texture_min_max.set_min_max_texture(texture_id, min_value, max_value)
421
+ elif min_value is None and max_value is None:
422
+ # Auto range: GPU reduction to single result texel, no readback.
423
+ min_max_tex = texture_min_max.compute_min_max_to_texture(texture_id)
424
+ else:
425
+ # Partial override (rare): the GPU path can't blend a caller value
426
+ # with a computed one, so fall back to the CPU reduction to get the
427
+ # missing bound, then upload the resolved range.
428
+ mn, mx = texture_min_max.get_texture_min_max(texture_id)
429
+ if min_value is None:
430
+ min_value = mn
431
+ if max_value is None:
432
+ max_value = mx
433
+ min_max_tex = texture_min_max.set_min_max_texture(texture_id, min_value, max_value)
434
+
435
+ result = self.apply(
436
+ 'normalize_remap',
437
+ texture_id,
438
+ in_place=in_place,
439
+ output_texture=output_texture,
440
+ min_max_tex=min_max_tex,
441
+ )
442
+ return result, min_value, max_value
443
+
444
+ def get_cache_stats(self) -> Dict[str, Any]:
445
+ """
446
+ Get statistics about all caches.
447
+
448
+ Returns:
449
+ Dict with the following keys:
450
+ - 'textures': Dict mapping input_texture_id -> (output_texture_id, width, height)
451
+ - 'fbos': Dict mapping (width, height) -> True (one FBO per size)
452
+ - 'temp_textures': Dict mapping (width, height) -> True (one temp per size)
453
+ """
454
+ return {
455
+ 'textures': dict(self._executor._texture_cache),
456
+ 'fbos': {
457
+ size: True
458
+ for size in self._executor._fbo_cache.keys()
459
+ },
460
+ 'temp_textures': {
461
+ size: True
462
+ for size in self._executor._temp_texture_cache.keys()
463
+ }
464
+ }
465
+
466
+ def cleanup(self) -> None:
467
+ """Clean up all OpenGL resources."""
468
+ self._compiler.cleanup()
469
+ self._executor.cleanup()
470
+ self._compiled.clear()
471
+
472
+ def __enter__(self) -> 'Filter':
473
+ """Context manager entry."""
474
+ return self
475
+
476
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
477
+ """Context manager exit - cleanup resources."""
478
+ self.cleanup()
479
+
480
+ def __dir__(self):
481
+ """List available attributes for tab completion."""
482
+ return (list(self._registry.shaders.keys()) +
483
+ ['apply', 'chain', 'compile_shader', 'compile_all',
484
+ 'has_shader', 'list_shaders', 'list_shader_types',
485
+ 'get_shader_info', 'use_shader', 'normalize',
486
+ 'clear_texture_cache', 'clear_fbo_cache', 'get_cache_stats', 'cleanup'])
487
+
488
+ def __repr__(self) -> str:
489
+ n_shaders = len(self._registry.shaders)
490
+ n_types = len(self._registry.shader_types)
491
+ n_compiled = len(self._compiled)
492
+ return f"Filter(shaders={n_shaders}, types={n_types}, compiled={n_compiled})"
493
+
494
+
495
+ import numpy as np
496
+ from OpenGL.GL import *
497
+ from OpenGL.GL import shaders
498
+ import ctypes
499
+
500
+
501
+ class TextureMinMax:
502
+ """Compute shader-based min/max finder for OpenGL textures."""
503
+
504
+ COMPUTE_SHADER_SOURCE = """
505
+ #version 430
506
+ layout(local_size_x = 16, local_size_y = 16) in;
507
+
508
+ layout(rgba32f, binding = 0) readonly uniform image2D inputImage;
509
+
510
+ layout(std430, binding = 1) buffer ResultBuffer {
511
+ vec2 workgroupResults[]; // Each workgroup writes (min, max)
512
+ };
513
+
514
+ uniform ivec2 numWorkgroups;
515
+
516
+ shared float localMin[256];
517
+ shared float localMax[256];
518
+
519
+ void main() {
520
+ ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
521
+ ivec2 size = imageSize(inputImage);
522
+ uint lid = gl_LocalInvocationIndex;
523
+
524
+ // Initialize with extreme values
525
+ float myMin = 1e38;
526
+ float myMax = -1e38;
527
+
528
+ if (pos.x < size.x && pos.y < size.y) {
529
+ vec4 pixel = imageLoad(inputImage, pos);
530
+ // Get min/max across RGB channels (ignore alpha)
531
+ myMin = min(min(pixel.r, pixel.g), pixel.b);
532
+ myMax = max(max(pixel.r, pixel.g), pixel.b);
533
+ }
534
+
535
+ localMin[lid] = myMin;
536
+ localMax[lid] = myMax;
537
+ barrier();
538
+
539
+ // Parallel reduction in shared memory
540
+ for (uint s = 128; s > 0; s >>= 1) {
541
+ if (lid < s) {
542
+ localMin[lid] = min(localMin[lid], localMin[lid + s]);
543
+ localMax[lid] = max(localMax[lid], localMax[lid + s]);
544
+ }
545
+ barrier();
546
+ }
547
+
548
+ // Workgroup leader writes result
549
+ if (lid == 0) {
550
+ uint workgroupIndex = gl_WorkGroupID.y * numWorkgroups.x + gl_WorkGroupID.x;
551
+ workgroupResults[workgroupIndex] = vec2(localMin[0], localMax[0]);
552
+ }
553
+ }
554
+ """
555
+
556
+ def __init__(self):
557
+ self._program = None
558
+ self._ssbo = None
559
+ self._initialized = False
560
+
561
+ def _ensure_initialized(self):
562
+ """Lazy initialization of shader and buffer."""
563
+ if self._initialized:
564
+ return
565
+
566
+ # Compile compute shader
567
+ compute_shader = shaders.compileShader(
568
+ self.COMPUTE_SHADER_SOURCE,
569
+ GL_COMPUTE_SHADER
570
+ )
571
+ self._program = shaders.compileProgram(compute_shader)
572
+
573
+ # Create SSBO (will resize as needed)
574
+ self._ssbo = glGenBuffers(1)
575
+ self._ssbo_size = 0
576
+
577
+ self._initialized = True
578
+
579
+ def get_min_max(self, texture_id: int, width: int, height: int) -> tuple[float, float]:
580
+ """
581
+ Compute min/max pixel values of an OpenGL texture.
582
+
583
+ Args:
584
+ texture_id: OpenGL texture ID (must be rgba32f format)
585
+ width: Texture width in pixels
586
+ height: Texture height in pixels
587
+
588
+ Returns:
589
+ Tuple of (min_value, max_value) across RGB channels
590
+ """
591
+ self._ensure_initialized()
592
+
593
+ # Calculate workgroup dimensions
594
+ workgroup_size = 16
595
+ num_workgroups_x = (width + workgroup_size - 1) // workgroup_size
596
+ num_workgroups_y = (height + workgroup_size - 1) // workgroup_size
597
+ total_workgroups = num_workgroups_x * num_workgroups_y
598
+
599
+ # Resize SSBO if needed (2 floats per workgroup: min and max)
600
+ required_size = total_workgroups * 2 * 4 # 2 floats * 4 bytes
601
+ if required_size > self._ssbo_size:
602
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, self._ssbo)
603
+ glBufferData(GL_SHADER_STORAGE_BUFFER, required_size, None, GL_DYNAMIC_READ)
604
+ self._ssbo_size = required_size
605
+
606
+ # Bind resources
607
+ glUseProgram(self._program)
608
+ glBindImageTexture(0, texture_id, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F)
609
+ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, self._ssbo)
610
+
611
+ # Set uniforms
612
+ loc = glGetUniformLocation(self._program, "numWorkgroups")
613
+ glUniform2i(loc, num_workgroups_x, num_workgroups_y)
614
+
615
+ # Dispatch compute shader
616
+ glDispatchCompute(num_workgroups_x, num_workgroups_y, 1)
617
+ glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT)
618
+
619
+ # Read back results
620
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, self._ssbo)
621
+ results = np.empty(total_workgroups * 2, dtype=np.float32)
622
+ glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, results.nbytes, results)
623
+
624
+ # Final reduction on CPU (very fast for small arrays)
625
+ results = results.reshape(-1, 2)
626
+ min_val = float(np.min(results[:, 0]))
627
+ max_val = float(np.max(results[:, 1]))
628
+
629
+ return min_val, max_val
630
+
631
+ def cleanup(self):
632
+ """Delete OpenGL resources."""
633
+ if self._ssbo is not None:
634
+ glDeleteBuffers(1, [self._ssbo])
635
+ self._ssbo = None
636
+ if self._program is not None:
637
+ glDeleteProgram(self._program)
638
+ self._program = None
639
+ self._initialized = False
640
+
641
+ def __del__(self):
642
+ # Note: OpenGL context must still be active for this to work
643
+ # Prefer explicit cleanup() call
644
+ pass
645
+
646
+
647
+ # Convenience functions for one global use
648
+ _global_instance = None
649
+
650
+
651
+ def get_texture_min_max(texture_id: int, width: int, height: int) -> tuple[float, float]:
652
+ """
653
+ Convenience function to get min/max of a texture.
654
+ Uses a cached shader program for efficiency.
655
+ """
656
+ global _global_instance
657
+ if _global_instance is None:
658
+ _global_instance = TextureMinMax()
659
+ return _global_instance.get_min_max(texture_id, width, height)
660
+
661
+
662
+ def cleanup_texture_min_max():
663
+ """Cleanup global resources."""
664
+ global _global_instance
665
+ if _global_instance is not None:
666
+ _global_instance.cleanup()
667
+ _global_instance = None