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,520 @@
1
+ """
2
+ Filter execution engine - applies shaders to textures.
3
+ """
4
+
5
+ from collections import OrderedDict
6
+ from typing import Dict, Any, Optional, Tuple
7
+ import ctypes
8
+
9
+ from meltygui.graphics.base import GLType
10
+ from meltygui.graphics.shader_compiler import ProgramCompiler
11
+ from meltygui.graphics.shader_compiler import CompiledProgram
12
+
13
+
14
+ def _get_gl():
15
+ """Lazy import of OpenGL."""
16
+ from OpenGL import GL
17
+ return GL
18
+
19
+
20
+ def _get_numpy():
21
+ """Lazy import of numpy."""
22
+ import numpy as np
23
+ return np
24
+
25
+
26
+ # Most recent distinct render-target sizes to keep FBOs and textures for.
27
+ # Big enough for any concurrently-used size in a frame (screen, shadow
28
+ # map-res, a few filtered textures); small enough that a resize drag's
29
+ # hundreds of intermediate sizes get evicted instead of accumulating.
30
+ _MAX_CACHED_SIZES = 8
31
+
32
+
33
+ class FilterExecutor:
34
+ """
35
+ Executes shader filters on textures.
36
+
37
+ Handles framebuffer setup, texture binding, and uniform passing.
38
+ """
39
+
40
+ def __init__(self, compiler: ProgramCompiler):
41
+ """
42
+ Initialize the filter executor.
43
+
44
+ Args:
45
+ compiler: The program compiler instance
46
+ """
47
+ self._compiler = compiler
48
+
49
+ # OpenGL resources
50
+ self._quad_vao = None
51
+ self._quad_vbo = None
52
+ self._initialized = False
53
+
54
+ # FBO cache: maps (width, height) -> FBO ID. LRU-bounded (see
55
+ # _MAX_CACHED_SIZES): a continuous window resize sweeps hundreds of
56
+ # unique sizes through here, and unbounded growth kept a full-size
57
+ # RGBA8 temp render texture per size - the resize VRAM leak.
58
+ self._fbo_cache: "OrderedDict[Tuple[int, int], int]" = OrderedDict()
59
+
60
+ # Temp texture cache for in-place operations: maps (width, height) -> texture ID
61
+ self._temp_texture_cache: "OrderedDict[Tuple[int, int], int]" = OrderedDict()
62
+
63
+ # Output texture cache: maps input_texture_id -> (output_texture_id, width, height)
64
+ # Each input texture gets its own persistent output texture
65
+ self._texture_cache: Dict[int, Tuple[int, int, int]] = {}
66
+
67
+ def _ensure_initialized(self) -> None:
68
+ """Lazily initialize OpenGL resources."""
69
+ if not self._initialized:
70
+ self._setup_quad()
71
+ self._initialized = True
72
+
73
+ def _setup_quad(self) -> None:
74
+ """Set up the fullscreen quad for rendering."""
75
+ GL = _get_gl()
76
+ np = _get_numpy()
77
+
78
+ # Fullscreen quad vertices: position (x, y, z) and texcoord (u, v)
79
+ quad_vertices = np.array([
80
+ # Position # TexCoord
81
+ -1.0, -1.0, 0.0, 0.0, 0.0,
82
+ 1.0, -1.0, 0.0, 1.0, 0.0,
83
+ 1.0, 1.0, 0.0, 1.0, 1.0,
84
+ -1.0, -1.0, 0.0, 0.0, 0.0,
85
+ 1.0, 1.0, 0.0, 1.0, 1.0,
86
+ -1.0, 1.0, 0.0, 0.0, 1.0,
87
+ ], dtype=np.float32)
88
+
89
+ self._quad_vao = GL.glGenVertexArrays(1)
90
+ self._quad_vbo = GL.glGenBuffers(1)
91
+
92
+ GL.glBindVertexArray(self._quad_vao)
93
+ GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self._quad_vbo)
94
+ GL.glBufferData(GL.GL_ARRAY_BUFFER, quad_vertices.nbytes, quad_vertices, GL.GL_STATIC_DRAW)
95
+
96
+ # Position attribute (location 0)
97
+ GL.glVertexAttribPointer(0, 3, GL.GL_FLOAT, GL.GL_FALSE, 20, ctypes.c_void_p(0))
98
+ GL.glEnableVertexAttribArray(0)
99
+
100
+ # TexCoord attribute (location 1)
101
+ GL.glVertexAttribPointer(1, 2, GL.GL_FLOAT, GL.GL_FALSE, 20, ctypes.c_void_p(12))
102
+ GL.glEnableVertexAttribArray(1)
103
+
104
+ GL.glBindVertexArray(0)
105
+
106
+ def execute(self, program: CompiledProgram, texture_id: int,
107
+ uniforms: Dict[str, Any], in_place: bool = False,
108
+ output_texture: Optional[int] = None,
109
+ output_framebuffer: Optional[int] = None,
110
+ input_framebuffer: Optional[int] = None,
111
+ output_size: Optional[Tuple[int, int]] = None) -> int:
112
+ """
113
+ Execute a shader filter on a texture or framebuffer.
114
+
115
+ Args:
116
+ program: The compiled shader program
117
+ texture_id: Input texture ID (ignored if input_framebuffer is set)
118
+ uniforms: Uniform values to pass to the shader.
119
+ For SAMPLER2D uniforms, pass the texture ID as the value.
120
+ in_place: If True, render back to the input texture
121
+ output_texture: Optional output texture ID (creates new if None and not in_place)
122
+ output_framebuffer: Optional framebuffer to render to (e.g., 0 for main screen).
123
+ input_framebuffer: Optional framebuffer to read from (e.g., 0 for main screen).
124
+ output_size: Optional (width, height) for the render target. When set, the
125
+ shader runs at this resolution while still sampling the full-resolution
126
+ input texture - i.e. fewer fragment invocations. Ignored for in_place
127
+ (the copy-back blit requires matching dimensions). The smaller output
128
+ texture is sampled with GL_LINEAR, so downstream passes upscale it
129
+ automatically. Useful for cheap, soft effects like shadows.
130
+
131
+ Returns:
132
+ The output texture ID (or 0 if rendering to output_framebuffer)
133
+ """
134
+ self._ensure_initialized()
135
+ GL = _get_gl()
136
+
137
+ original_framebuffer = GL.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING)
138
+ original_texture = GL.glGetIntegerv(GL.GL_TEXTURE_BINDING_2D)
139
+ original_program = GL.glGetIntegerv(GL.GL_CURRENT_PROGRAM)
140
+ original_vao = GL.glGetIntegerv(GL.GL_VERTEX_ARRAY_BINDING)
141
+ original_viewport = GL.glGetIntegerv(GL.GL_VIEWPORT)
142
+
143
+ GL.glDisable(GL.GL_DEPTH_TEST)
144
+
145
+ # Handle input framebuffer - get texture from framebuffer if specified.
146
+ # Reading and writing the SAME framebuffer (the in-place filters over
147
+ # the scene target in scene_target.py) must go through a copy: the
148
+ # attachment shortcut would hand back the very texture the pass is
149
+ # about to clear and draw into, and the filter samples zeros.
150
+ if input_framebuffer is not None:
151
+ texture_id, width, height = self._get_texture_from_framebuffer(
152
+ input_framebuffer, force_copy=(input_framebuffer == output_framebuffer))
153
+ else:
154
+ # Get texture dimensions from texture_id
155
+ GL.glBindTexture(GL.GL_TEXTURE_2D, texture_id)
156
+ width = GL.glGetTexLevelParameteriv(GL.GL_TEXTURE_2D, 0, GL.GL_TEXTURE_WIDTH)
157
+ height = GL.glGetTexLevelParameteriv(GL.GL_TEXTURE_2D, 0, GL.GL_TEXTURE_HEIGHT)
158
+
159
+ # Resolution of the render target. Defaults to the input size, but can be
160
+ # downscaled via output_size. The input texture is still sampled at full
161
+ # resolution (via normalized UV coordinates) regardless of the render size.
162
+ if output_size is not None and not in_place:
163
+ render_width = max(1, int(output_size[0]))
164
+ render_height = max(1, int(output_size[1]))
165
+ else:
166
+ render_width, render_height = width, height
167
+
168
+ # Handle direct framebuffer rendering (e.g., to main screen)
169
+ if output_framebuffer is not None:
170
+ GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, output_framebuffer)
171
+ GL.glViewport(0, 0, render_width, render_height)
172
+ out_tex = 0
173
+ else:
174
+ fbo, temp_texture = self._get_fbo(render_width, render_height)
175
+
176
+ if in_place:
177
+ out_tex = temp_texture
178
+ elif output_texture is not None:
179
+ out_tex = output_texture
180
+ else:
181
+ out_tex = self._get_cached_texture(texture_id, render_width, render_height)
182
+
183
+ GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, fbo)
184
+ GL.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0,
185
+ GL.GL_TEXTURE_2D, out_tex, 0)
186
+ GL.glViewport(0, 0, render_width, render_height)
187
+
188
+ GL.glClear(GL.GL_COLOR_BUFFER_BIT)
189
+ GL.glUseProgram(program.program_id)
190
+
191
+ # Bind main input texture to unit 0
192
+ GL.glActiveTexture(GL.GL_TEXTURE0)
193
+ GL.glBindTexture(GL.GL_TEXTURE_2D, texture_id)
194
+ GL.glUniform1i(program.uniform_locations['u_texture'], 0)
195
+
196
+ # Bind additional sampler textures to units 1, 2, 3, ...
197
+ next_texture_unit = 1
198
+ sampler_bindings = {} # Track which uniforms are samplers and their units
199
+
200
+ for name, (gl_type, _) in program.shader.uniforms.items():
201
+ if gl_type == GLType.SAMPLER2D and name in uniforms:
202
+ sampler_texture_id = uniforms[name]
203
+ if sampler_texture_id is not None:
204
+ GL.glActiveTexture(GL.GL_TEXTURE0 + next_texture_unit)
205
+ GL.glBindTexture(GL.GL_TEXTURE_2D, sampler_texture_id)
206
+ sampler_bindings[name] = next_texture_unit
207
+ next_texture_unit += 1
208
+
209
+ # Auto-populate texture_size if needed. Use the render target size so any
210
+ # texel-space math (e.g. blur kernels) matches the resolution we render at.
211
+ if 'texture_size' in program.shader.uniforms and 'texture_size' not in uniforms:
212
+ uniforms = {**uniforms, 'texture_size': (float(render_width), float(render_height))}
213
+
214
+ # Set uniforms (passing sampler bindings for texture unit assignment)
215
+ self._set_uniforms(program, uniforms, sampler_bindings)
216
+
217
+ # Draw fullscreen quad
218
+ GL.glBindVertexArray(self._quad_vao)
219
+ GL.glDrawArrays(GL.GL_TRIANGLES, 0, 6)
220
+ GL.glBindVertexArray(0)
221
+
222
+ # Restore state
223
+ GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, original_framebuffer)
224
+ GL.glActiveTexture(GL.GL_TEXTURE0) # Reset to texture unit 0
225
+ GL.glBindTexture(GL.GL_TEXTURE_2D, original_texture)
226
+ GL.glUseProgram(original_program)
227
+ GL.glBindVertexArray(original_vao)
228
+ GL.glViewport(original_viewport[0], original_viewport[1],
229
+ original_viewport[2], original_viewport[3])
230
+
231
+ if output_framebuffer is not None:
232
+ return 0
233
+
234
+ if in_place:
235
+ self._copy_texture(out_tex, texture_id, width, height)
236
+ return texture_id
237
+
238
+ return out_tex
239
+
240
+ def _set_uniforms(self, program: CompiledProgram, uniforms: Dict[str, Any],
241
+ sampler_bindings: Optional[Dict[str, int]] = None) -> None:
242
+ """Set uniform values."""
243
+ if sampler_bindings is None:
244
+ sampler_bindings = {}
245
+
246
+ for name, value in uniforms.items():
247
+ if name not in program.uniform_locations:
248
+ continue
249
+
250
+ loc = program.uniform_locations[name]
251
+ if loc < 0:
252
+ continue
253
+
254
+ gl_type, _ = program.shader.uniforms.get(name, (None, None))
255
+ if gl_type is None:
256
+ continue
257
+
258
+ # Handle samplers specially - set to texture unit, not texture ID
259
+ if gl_type == GLType.SAMPLER2D:
260
+ if name in sampler_bindings:
261
+ self._set_uniform_value(loc, GLType.INT, sampler_bindings[name])
262
+ continue
263
+
264
+ self._set_uniform_value(loc, gl_type, value)
265
+
266
+ def _set_uniform_value(self, location: int, gl_type: GLType, value: Any) -> None:
267
+ """Set a single uniform value."""
268
+ GL = _get_gl()
269
+
270
+ if gl_type == GLType.FLOAT:
271
+ GL.glUniform1f(location, float(value))
272
+ elif gl_type == GLType.VEC2:
273
+ GL.glUniform2f(location, *value)
274
+ elif gl_type == GLType.VEC3:
275
+ GL.glUniform3f(location, *value)
276
+ elif gl_type == GLType.VEC4:
277
+ GL.glUniform4f(location, *value)
278
+ elif gl_type == GLType.INT:
279
+ GL.glUniform1i(location, int(value))
280
+ elif gl_type == GLType.BOOL:
281
+ GL.glUniform1i(location, 1 if value else 0)
282
+ elif gl_type == GLType.MAT4:
283
+ GL.glUniformMatrix4fv(location, 1, GL.GL_FALSE, value)
284
+
285
+ def _get_fbo(self, width: int, height: int) -> Tuple[int, int]:
286
+ """
287
+ Get or create an FBO and temp texture for the given size.
288
+
289
+ Args:
290
+ width: Texture width
291
+ height: Texture height
292
+
293
+ Returns:
294
+ Tuple of (fbo_id, temp_texture_id)
295
+ """
296
+ GL = _get_gl()
297
+ size = (width, height)
298
+
299
+ # Get or create FBO for this size
300
+ if size not in self._fbo_cache:
301
+ self._fbo_cache[size] = GL.glGenFramebuffers(1)
302
+
303
+ # Get or create temp texture for this size
304
+ if size not in self._temp_texture_cache:
305
+ self._temp_texture_cache[size] = self._create_texture(width, height)
306
+
307
+ # LRU-bound both caches: evict the least recently used sizes so a
308
+ # window resize (a new size every frame) can't accumulate a temp
309
+ # texture + FBO per intermediate size. Runs on the GL thread (only
310
+ # execute() calls this), so deleting here is safe.
311
+ self._fbo_cache.move_to_end(size)
312
+ self._temp_texture_cache.move_to_end(size)
313
+ while len(self._fbo_cache) > _MAX_CACHED_SIZES:
314
+ _, old_fbo = self._fbo_cache.popitem(last=False)
315
+ GL.glDeleteFramebuffers(1, [old_fbo])
316
+ while len(self._temp_texture_cache) > _MAX_CACHED_SIZES:
317
+ _, old_tex = self._temp_texture_cache.popitem(last=False)
318
+ GL.glDeleteTextures(1, [old_tex])
319
+
320
+ return self._fbo_cache[size], self._temp_texture_cache[size]
321
+
322
+ def _create_texture(self, width: int, height: int) -> int:
323
+ """Create a new texture."""
324
+ GL = _get_gl()
325
+
326
+ texture = GL.glGenTextures(1)
327
+ GL.glBindTexture(GL.GL_TEXTURE_2D, texture)
328
+ # fp16: the filters run over a linear scRGB scene (scene_target.py)
329
+ GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA16F, width, height, 0,
330
+ GL.GL_RGBA, GL.GL_HALF_FLOAT, None)
331
+ GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR)
332
+ GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR)
333
+ GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE)
334
+ GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE)
335
+ return texture
336
+
337
+ def _get_cached_texture(self, input_texture_id: int, width: int, height: int) -> int:
338
+ """
339
+ Get or create an output texture for the given input texture.
340
+
341
+ Each input texture ID gets its own persistent output texture.
342
+ If the size changes, the old texture is deleted and a new one is created.
343
+
344
+ Args:
345
+ input_texture_id: The input texture ID
346
+ width: Desired texture width
347
+ height: Desired texture height
348
+
349
+ Returns:
350
+ Output texture ID
351
+ """
352
+ GL = _get_gl()
353
+
354
+ # Check if we have a cached texture for this input
355
+ if input_texture_id in self._texture_cache:
356
+ output_texture_id, cached_width, cached_height = self._texture_cache[input_texture_id]
357
+
358
+ # Check if size matches
359
+ if cached_width == width and cached_height == height:
360
+ return output_texture_id
361
+
362
+ # Size changed - delete old texture
363
+ GL.glDeleteTextures(1, [output_texture_id])
364
+
365
+ # Create new texture and cache it
366
+ new_texture = self._create_texture(width, height)
367
+ self._texture_cache[input_texture_id] = (new_texture, width, height)
368
+ return new_texture
369
+
370
+ def _get_texture_from_framebuffer(self, framebuffer_id: int, force_copy: bool = False) -> Tuple[int, int, int]:
371
+ """
372
+ Get a texture from a framebuffer's color attachment, or create one from its contents.
373
+
374
+ Args:
375
+ framebuffer_id: The framebuffer to read from (0 for main screen)
376
+ force_copy: Never return the attachment itself — copy the contents
377
+ (required when the caller renders back into this framebuffer)
378
+
379
+ Returns:
380
+ Tuple of (texture_id, width, height)
381
+ """
382
+ GL = _get_gl()
383
+
384
+ # Save current bindings
385
+ original_fbo = GL.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING)
386
+ original_read_fbo = GL.glGetIntegerv(GL.GL_READ_FRAMEBUFFER_BINDING)
387
+
388
+ # Bind the framebuffer
389
+ GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, framebuffer_id)
390
+
391
+ # Get viewport dimensions (works for both 0 and FBOs)
392
+ viewport = GL.glGetIntegerv(GL.GL_VIEWPORT)
393
+ width = viewport[2]
394
+ height = viewport[3]
395
+
396
+ # Try to get the texture attachment (for non-zero FBOs)
397
+ texture_id = None
398
+ if framebuffer_id != 0 and not force_copy:
399
+ try:
400
+ # Check what's attached to COLOR_ATTACHMENT0
401
+ attachment_type = GL.glGetFramebufferAttachmentParameteriv(
402
+ GL.GL_FRAMEBUFFER,
403
+ GL.GL_COLOR_ATTACHMENT0,
404
+ GL.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE
405
+ )
406
+
407
+ # If it's a texture, get the texture name
408
+ if attachment_type == GL.GL_TEXTURE:
409
+ texture_id = GL.glGetFramebufferAttachmentParameteriv(
410
+ GL.GL_FRAMEBUFFER,
411
+ GL.GL_COLOR_ATTACHMENT0,
412
+ GL.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME
413
+ )
414
+ except:
415
+ # Attachment query failed, we'll copy instead
416
+ texture_id = None
417
+
418
+ # If we got a texture attachment, use it directly
419
+ if texture_id is not None:
420
+ GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, original_fbo)
421
+ GL.glBindFramebuffer(GL.GL_READ_FRAMEBUFFER, original_read_fbo)
422
+ return (texture_id, width, height)
423
+
424
+ # Otherwise, copy framebuffer contents to a temporary texture.
425
+ # ONE copy target per framebuffer, reused when that framebuffer's
426
+ # size changes - framebuffer 0 is read every filtered frame and
427
+ # resizes with the window, and the old per-(fb, w, h) keys leaked a
428
+ # full-screen texture for every intermediate size during a resize.
429
+ if not hasattr(self, '_fbo_input_cache'):
430
+ self._fbo_input_cache = {}
431
+
432
+ cached = self._fbo_input_cache.get(framebuffer_id)
433
+ if cached is not None and cached[1] == width and cached[2] == height:
434
+ temp_texture = cached[0]
435
+ else:
436
+ if cached is not None:
437
+ GL.glDeleteTextures(1, [cached[0]])
438
+ temp_texture = self._create_texture(width, height)
439
+ self._fbo_input_cache[framebuffer_id] = (temp_texture, width, height)
440
+
441
+ # Copy framebuffer contents to the texture
442
+ GL.glBindFramebuffer(GL.GL_READ_FRAMEBUFFER, framebuffer_id)
443
+ GL.glBindTexture(GL.GL_TEXTURE_2D, temp_texture)
444
+ GL.glCopyTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA16F, 0, 0, width, height, 0)
445
+
446
+ # Restore bindings
447
+ GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, original_fbo)
448
+ GL.glBindFramebuffer(GL.GL_READ_FRAMEBUFFER, original_read_fbo)
449
+
450
+ return (temp_texture, width, height)
451
+
452
+ def clear_texture_cache(self) -> None:
453
+ """Clear all cached output textures."""
454
+ GL = _get_gl()
455
+
456
+ for output_texture_id, width, height in self._texture_cache.values():
457
+ GL.glDeleteTextures(1, [output_texture_id])
458
+
459
+ self._texture_cache.clear()
460
+
461
+ def clear_fbo_cache(self) -> None:
462
+ """Clear all cached FBOs and temp textures."""
463
+ GL = _get_gl()
464
+
465
+ # Clean up all cached FBOs
466
+ for fbo_id in self._fbo_cache.values():
467
+ GL.glDeleteFramebuffers(1, [fbo_id])
468
+ self._fbo_cache.clear()
469
+
470
+ # Clean up all temp textures
471
+ for texture_id in self._temp_texture_cache.values():
472
+ GL.glDeleteTextures(1, [texture_id])
473
+ self._temp_texture_cache.clear()
474
+
475
+ def _copy_texture(self, src: int, dst: int, width: int, height: int) -> None:
476
+ """Copy texture data from src to dst."""
477
+ GL = _get_gl()
478
+
479
+ # Bind source to read framebuffer
480
+ read_fbo = GL.glGenFramebuffers(1)
481
+ GL.glBindFramebuffer(GL.GL_READ_FRAMEBUFFER, read_fbo)
482
+ GL.glFramebufferTexture2D(GL.GL_READ_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0,
483
+ GL.GL_TEXTURE_2D, src, 0)
484
+
485
+ # Bind destination to draw framebuffer
486
+ draw_fbo = GL.glGenFramebuffers(1)
487
+ GL.glBindFramebuffer(GL.GL_DRAW_FRAMEBUFFER, draw_fbo)
488
+ GL.glFramebufferTexture2D(GL.GL_DRAW_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0,
489
+ GL.GL_TEXTURE_2D, dst, 0)
490
+
491
+ # Blit
492
+ GL.glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
493
+ GL.GL_COLOR_BUFFER_BIT, GL.GL_NEAREST)
494
+
495
+ # Cleanup
496
+ GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0)
497
+ GL.glDeleteFramebuffers(2, [read_fbo, draw_fbo])
498
+
499
+ def cleanup(self) -> None:
500
+ """Clean up all resources."""
501
+ GL = _get_gl()
502
+
503
+ if self._initialized:
504
+ if self._quad_vao is not None:
505
+ GL.glDeleteVertexArrays(1, [self._quad_vao])
506
+ if self._quad_vbo is not None:
507
+ GL.glDeleteBuffers(1, [self._quad_vbo])
508
+
509
+ # Clean up input framebuffer cache. Values are (texture, w, h); a
510
+ # copy-swapped-over instance may still hold bare texture ids from the
511
+ # old per-size cache, so accept both shapes.
512
+ if hasattr(self, '_fbo_input_cache'):
513
+ for entry in self._fbo_input_cache.values():
514
+ texture_id = entry[0] if isinstance(entry, tuple) else entry
515
+ GL.glDeleteTextures(1, [texture_id])
516
+ self._fbo_input_cache.clear()
517
+
518
+ # Clean up all caches
519
+ self.clear_fbo_cache()
520
+ self.clear_texture_cache()