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,478 @@
1
+ """@shader_func — kwargs-driven GLSL uniform injection.
2
+
3
+ Melty's parameter injection means a value travels by NAME from the call site
4
+ into whatever declares that name. shader_func extends the same idea into the
5
+ shader: the GLSL never declares plumbing uniforms — any kwarg whose name
6
+ appears as an identifier in a stage's source gets a `uniform <type> <name>;`
7
+ spliced in (type inferred from the Python value), and is set every call via
8
+ the program's own reflection table.
9
+
10
+ VOXEL_FRAG = '''
11
+ #version 330 core
12
+ out vec4 FragColor;
13
+ void main() { FragColor = vec4(uv * brightness, tilt, 1.0); }
14
+ '''
15
+
16
+ @shader_func(fragment=VOXEL_FRAG)
17
+ def voxel_pass(gl_state: GLState = None, brightness=0.64, tilt=0.0,
18
+ volume_texture=None, **kwargs):
19
+ # program is already bound, uniforms already set — body is draw calls
20
+ gl.glBindVertexArray(gl_state.vao("fs_triangle"))
21
+ gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3)
22
+
23
+ # inside a @render_func, with gl_state auto-injected:
24
+ voxel_pass(gl_state, brightness=brightness, tilt=tilt)
25
+
26
+ How it decides what to declare/set, per call:
27
+ - candidate values = body signature defaults overlaid with call kwargs
28
+ - a candidate becomes a uniform iff its name appears in a stage's
29
+ (comment-stripped) identifiers AND its value maps to a GLSL type
30
+ (infer_glsl_type — strings/objects return None and are simply body kwargs)
31
+ - already-declared uniforms in the source are never re-declared, just set
32
+ - setting is driven by glGetActiveUniform reflection, so over-matching is
33
+ harmless: a name the compiler optimized out has no location and is skipped
34
+
35
+ There is deliberately no GLSL parser here. The transformation is purely
36
+ additive (declarations after the #version/#extension block) and the GL
37
+ compiler is the validator — its errors are surfaced with line numbers
38
+ remapped to the original source.
39
+
40
+ Compile lifecycle rides GLState.get with deps = the generated sources:
41
+ editing the GLSL (hotswap re-runs the decorator) changes the deps, so the
42
+ program recompiles next call and the old one is queued for deletion. A failed
43
+ compile keeps the last good program running (GLState's last-good semantics),
44
+ stores the remapped log on `.last_error`, and doesn't retry until the source
45
+ actually changes.
46
+
47
+ Threading: GL is main-thread-only in this app. Off the main thread (chain
48
+ converters, Background runs) a shader_func call is a silent no-op returning
49
+ None.
50
+ """
51
+
52
+ import inspect
53
+ import re
54
+
55
+ import numpy as np
56
+ import OpenGL.GL as gl
57
+
58
+ from meltygui.core.graphics.gl_state import GLState
59
+ from meltygui.core.graphics.gl_state import GLTexture
60
+ from meltygui.core.graphics.gl_state import _scalar
61
+ from meltygui.core.graphics.gl_state import is_gl_thread
62
+ from meltygui.model.texture_model import TextureId
63
+
64
+ # Fullscreen triangle for fragment-only shader_funcs (no VBO needed; core
65
+ # profile still requires a VAO bound - gl_state.vao(key) provides an empty one).
66
+ DEFAULT_VERTEX_FULLSCREEN = """
67
+ #version 330 core
68
+ out vec2 uv;
69
+ void main() {
70
+ vec2 pos = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
71
+ uv = pos;
72
+ gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0);
73
+ }
74
+ """
75
+
76
+
77
+ class ShaderError(RuntimeError):
78
+ """Shader compile/link failure, message already remapped to original
79
+ source line numbers."""
80
+
81
+
82
+ # ── GLSL source analysis (pure logic, unit-tested without GL) ────────────
83
+
84
+ _DIRECTIVE_RE = re.compile(r"^\s*#\s*(version|extension)\b")
85
+ _IDENT_RE = re.compile(r"\b[A-Za-z_]\w*\b")
86
+ # `uniform ... name;` - possibly prefixed by layout/precision qualifiers,
87
+ # possibly an array. [^;{}]* keeps us out of interface blocks. Multi-declarator
88
+ # lines (`uniform float a, b;`) only register the last name - declare such
89
+ # uniforms yourself.
90
+ _UNIFORM_DECL_RE = re.compile(r"\buniform\b[^;{}]*?(\w+)\s*(?:\[[^\]]*\])?\s*;")
91
+
92
+
93
+ def strip_comments(source):
94
+ """GLSL minus // and /* */ comments, line structure preserved (GLSL has
95
+ no string literals, so this can't over-strip)."""
96
+ source = re.sub(r"/\*.*?\*/", lambda m: "\n" * m.group(0).count("\n"), source, flags=re.S)
97
+ return re.sub(r"//[^\n]*", "", source)
98
+
99
+
100
+ def glsl_identifiers(source):
101
+ return frozenset(_IDENT_RE.findall(strip_comments(source)))
102
+
103
+
104
+ def declared_uniform_names(source):
105
+ return frozenset(m.group(1) for m in _UNIFORM_DECL_RE.finditer(strip_comments(source)))
106
+
107
+
108
+ def infer_glsl_type(value):
109
+ """GLSL uniform type for a Python value, or None if the value isn't
110
+ uniform-able (strings, objects, None — those stay plain body kwargs)."""
111
+ if isinstance(value, (bool, np.bool_)):
112
+ return "bool"
113
+ if isinstance(value, (int, np.integer)):
114
+ return "int"
115
+ if isinstance(value, (float, np.floating)):
116
+ return "float"
117
+ # Name-compared (not isinstance) so textures created before a hotswap of
118
+ # gl_state.py keep working the same logic as set_default's type check.
119
+ if type(value).__name__ == "GLTexture" or isinstance(value, TextureId):
120
+ if value.target == int(gl.GL_TEXTURE_3D):
121
+ return "sampler3D"
122
+ if value.target == int(gl.GL_TEXTURE_1D):
123
+ return "sampler1D"
124
+ if value.target == int(gl.GL_TEXTURE_CUBE_MAP):
125
+ return "samplerCube"
126
+ return "sampler2D"
127
+ tname = type(value).__name__
128
+ if type(value).__module__ == "glm":
129
+ if tname.startswith("d"): # dvec3/dmat4x4 - setter upcasts to f32
130
+ tname = tname[1:]
131
+ if tname in ("vec2", "vec3", "vec4",
132
+ "ivec2", "ivec3", "ivec4", "bvec2", "bvec3", "bvec4"):
133
+ return tname
134
+ square = re.fullmatch(r"mat(\d)(?:x(\d))?", tname)
135
+ if square and (square.group(2) is None or square.group(2) == square.group(1)):
136
+ return f"mat{square.group(1)}"
137
+ return None
138
+ if isinstance(value, (tuple, list)):
139
+ if 2 <= len(value) <= 4 and all(isinstance(v, (int, float, np.integer, np.floating))
140
+ for v in value):
141
+ return f"vec{len(value)}"
142
+ return None
143
+ if isinstance(value, np.ndarray):
144
+ if value.ndim == 1 and 2 <= value.shape[0] <= 4:
145
+ return f"vec{value.shape[0]}"
146
+ if value.ndim == 2 and value.shape[0] == value.shape[1] and 2 <= value.shape[0] <= 4:
147
+ return f"mat{value.shape[0]}"
148
+ return None
149
+ return None
150
+
151
+
152
+ def inject_uniforms(source, decls):
153
+ """Splice `uniform <type> <name>;` lines (sorted, for stable deps hashing)
154
+ right after the #version/#extension block. Returns (generated_source,
155
+ insert_line_index, n_inserted_lines) — the last two drive error-log line
156
+ remapping."""
157
+ lines = source.split("\n")
158
+ idx = 0
159
+ for i, line in enumerate(lines):
160
+ if _DIRECTIVE_RE.match(line):
161
+ idx = i + 1
162
+ if not decls:
163
+ return source, idx, 0
164
+ block = ["// shader_func: auto-injected uniforms"]
165
+ block += [f"uniform {glsl_type} {name};" for name, glsl_type in sorted(decls)]
166
+ return "\n".join(lines[:idx] + block + lines[idx:]), idx, len(block)
167
+
168
+
169
+ def _remap_log(log, insert_line, n_inserted):
170
+ """Rewrite driver error line numbers (NVIDIA `0(57)`, Mesa `0:57(8)`)
171
+ back to the original, pre-injection source."""
172
+ if n_inserted == 0:
173
+ return log
174
+
175
+ def fix(ln):
176
+ ln = int(ln)
177
+ if ln > insert_line + n_inserted:
178
+ return str(ln - n_inserted)
179
+ if ln > insert_line:
180
+ return f"{ln}<injected uniform block>"
181
+ return str(ln)
182
+
183
+ new, count = re.subn(r"(\d+):(\d+)(\()",
184
+ lambda m: f"{m.group(1)}:{fix(m.group(2))}{m.group(3)}", log)
185
+ if count:
186
+ return new
187
+ new, _ = re.subn(r"(\d+)\((\d+)\)",
188
+ lambda m: f"{m.group(1)}({fix(m.group(2))})", log)
189
+ return new
190
+
191
+
192
+ # ── compile / link / set ──────────────────────────────────────────────
193
+
194
+ def _compile_stage(kind, enum, generated, insert_line, n_inserted):
195
+ shader = _scalar(gl.glCreateShader(enum))
196
+ gl.glShaderSource(shader, generated)
197
+ gl.glCompileShader(shader)
198
+ if not gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS):
199
+ log = gl.glGetShaderInfoLog(shader)
200
+ gl.glDeleteShader(shader)
201
+ log = log.decode(errors="replace") if isinstance(log, bytes) else str(log)
202
+ raise ShaderError(f"{kind} shader: {_remap_log(log.strip(), insert_line, n_inserted)}")
203
+ return shader
204
+
205
+
206
+ def _link_program(stage_shaders):
207
+ program = _scalar(gl.glCreateProgram())
208
+ for shader in stage_shaders:
209
+ gl.glAttachShader(program, shader)
210
+ gl.glLinkProgram(program)
211
+ for shader in stage_shaders:
212
+ gl.glDetachShader(program, shader)
213
+ gl.glDeleteShader(shader)
214
+ if not gl.glGetProgramiv(program, gl.GL_LINK_STATUS):
215
+ log = gl.glGetProgramInfoLog(program)
216
+ gl.glDeleteProgram(program)
217
+ log = log.decode(errors="replace") if isinstance(log, bytes) else str(log)
218
+ raise ShaderError(f"link: {log.strip()}")
219
+ return program
220
+
221
+
222
+ _SAMPLER_TARGETS = {
223
+ int(gl.GL_SAMPLER_1D): gl.GL_TEXTURE_1D,
224
+ int(gl.GL_SAMPLER_2D): gl.GL_TEXTURE_2D,
225
+ int(gl.GL_SAMPLER_3D): gl.GL_TEXTURE_3D,
226
+ int(gl.GL_SAMPLER_CUBE): gl.GL_TEXTURE_CUBE_MAP,
227
+ int(gl.GL_SAMPLER_2D_ARRAY): gl.GL_TEXTURE_2D_ARRAY,
228
+ int(gl.GL_SAMPLER_2D_SHADOW): gl.GL_TEXTURE_2D, # depth texture (pbr shadow maps)
229
+ }
230
+
231
+
232
+ def reflect_uniforms(program):
233
+ """{name: (location, gl_type, size)} for every active uniform — the
234
+ ground truth for what to set. Anything the compiler optimized out simply
235
+ isn't here."""
236
+ out = {}
237
+ count = _scalar(gl.glGetProgramiv(program, gl.GL_ACTIVE_UNIFORMS))
238
+ for i in range(count):
239
+ name, size, utype = gl.glGetActiveUniform(program, i)
240
+ name = name.decode(errors="replace") if isinstance(name, bytes) else str(name)
241
+ name = name.split("[")[0]
242
+ loc = gl.glGetUniformLocation(program, name)
243
+ if loc >= 0:
244
+ out[name] = (int(loc), int(utype), int(size))
245
+ return out
246
+
247
+
248
+ def _as_f32(v):
249
+ return np.ascontiguousarray(v, dtype=np.float32)
250
+
251
+
252
+ def _as_i32(v):
253
+ return np.ascontiguousarray(v, dtype=np.int32)
254
+
255
+
256
+ def _set_matrix(fn, loc, val):
257
+ if type(val).__module__ == "glm":
258
+ # PyGLM uses the buffer protocol with column-major memory: upload
259
+ # as-is. Row-major numpy math matrices need the transpose flag.
260
+ fn(loc, 1, gl.GL_FALSE, np.asarray(val, dtype=np.float32))
261
+ else:
262
+ fn(loc, 1, gl.GL_TRUE, _as_f32(val))
263
+
264
+
265
+ _SETTERS = {
266
+ int(gl.GL_FLOAT): lambda loc, v: gl.glUniform1f(loc, float(v)),
267
+ int(gl.GL_INT): lambda loc, v: gl.glUniform1i(loc, int(v)),
268
+ int(gl.GL_UNSIGNED_INT): lambda loc, v: gl.glUniform1ui(loc, int(v)),
269
+ int(gl.GL_BOOL): lambda loc, v: gl.glUniform1i(loc, 1 if v else 0),
270
+ int(gl.GL_FLOAT_VEC2): lambda loc, v: gl.glUniform2fv(loc, 1, _as_f32(v)),
271
+ int(gl.GL_FLOAT_VEC3): lambda loc, v: gl.glUniform3fv(loc, 1, _as_f32(v)),
272
+ int(gl.GL_FLOAT_VEC4): lambda loc, v: gl.glUniform4fv(loc, 1, _as_f32(v)),
273
+ int(gl.GL_INT_VEC2): lambda loc, v: gl.glUniform2iv(loc, 1, _as_i32(v)),
274
+ int(gl.GL_INT_VEC3): lambda loc, v: gl.glUniform3iv(loc, 1, _as_i32(v)),
275
+ int(gl.GL_INT_VEC4): lambda loc, v: gl.glUniform4iv(loc, 1, _as_i32(v)),
276
+ int(gl.GL_FLOAT_MAT2): lambda loc, v: _set_matrix(gl.glUniformMatrix2fv, loc, v),
277
+ int(gl.GL_FLOAT_MAT3): lambda loc, v: _set_matrix(gl.glUniformMatrix3fv, loc, v),
278
+ int(gl.GL_FLOAT_MAT4): lambda loc, v: _set_matrix(gl.glUniformMatrix4fv, loc, v),
279
+ }
280
+
281
+
282
+ class _Stage:
283
+ __slots__ = ("kind", "enum", "source", "identifiers", "predeclared")
284
+
285
+ def __init__(self, kind, enum, source):
286
+ self.kind = kind
287
+ self.enum = enum
288
+ self.source = source
289
+ self.identifiers = glsl_identifiers(source)
290
+ self.predeclared = declared_uniform_names(source)
291
+
292
+
293
+ class _ProgramRecord:
294
+ __slots__ = ("program", "uniforms", "sampler_units")
295
+
296
+ def __init__(self, program):
297
+ self.program = program
298
+ self.uniforms = reflect_uniforms(program)
299
+ # Texture units assigned by sorted uniform name - deterministic across
300
+ # recompiles, so a given sampler keeps its unit as the source evolves.
301
+ self.sampler_units = {
302
+ name: unit for unit, name in enumerate(sorted(
303
+ n for n, (_, t, _s) in self.uniforms.items() if t in _SAMPLER_TARGETS))
304
+ }
305
+
306
+
307
+ class ShaderFunc:
308
+
309
+ def __init__(self, func, fragment, vertex=None, name=None):
310
+ if not fragment:
311
+ raise TypeError("@shader_func requires fragment GLSL: @shader_func(fragment=...)")
312
+ self._func = func
313
+ self._name = name or f"{func.__module__}.{func.__qualname__}"
314
+ self.__name__ = getattr(func, "__name__", self._name)
315
+ self.__doc__ = getattr(func, "__doc__", None)
316
+ self.stages = [
317
+ _Stage("vertex", gl.GL_VERTEX_SHADER, vertex or DEFAULT_VERTEX_FULLSCREEN),
318
+ _Stage("fragment", gl.GL_FRAGMENT_SHADER, fragment),
319
+ ]
320
+
321
+ sig = inspect.signature(func)
322
+ self._wants_var_kwargs = any(p.kind is inspect.Parameter.VAR_KEYWORD
323
+ for p in sig.parameters.values())
324
+ self._body_params = {n for n, p in sig.parameters.items()
325
+ if p.kind not in (inspect.Parameter.VAR_KEYWORD,
326
+ inspect.Parameter.VAR_POSITIONAL)}
327
+ self._defaults = {n: p.default for n, p in sig.parameters.items()
328
+ if p.default is not inspect.Parameter.empty}
329
+ self._skip = {"gl_state", "program"}
330
+
331
+ self._gen_cache = {} # tuple of decl tuples -> [(src, idx, n)]
332
+ # Failure latch is per (deps, gl_state): a bad source is retried at
333
+ # most ONCE PER GLSTATE - never globally. A global latch starved every
334
+ # window created after one transient failure (no last-good record →
335
+ # silent None → permanently empty view) while older windows kept
336
+ # rendering on cached programs.
337
+ self._failed_deps = None
338
+ self._failed_states = set() # id(gl_state) that has tried _failed_deps
339
+ self.last_error = None
340
+ self.last_generated = {} # stage kind -> generated source (debugging)
341
+
342
+ def _generated_stages(self, stage_decls):
343
+ key = tuple(tuple(sorted(d)) for d in stage_decls)
344
+ gen = self._gen_cache.get(key)
345
+ if gen is None:
346
+ gen = [inject_uniforms(stage.source, decls)
347
+ for stage, decls in zip(self.stages, stage_decls)]
348
+ self._gen_cache[key] = gen
349
+ return gen
350
+
351
+ def __call__(self, gl_state: GLState = None, **kwargs):
352
+ # Duck-typed (hotswap of gl_state.py leaves live instances of the
353
+ # old class, so typing would spuriously fail).
354
+ if gl_state is None or not (hasattr(gl_state, "get") and hasattr(gl_state, "peek")):
355
+ raise TypeError(
356
+ f"shader_func {self.__name__!r} needs a GLState: declare "
357
+ f"`gl_state: GLState = None` on the enclosing @render_func and "
358
+ f"forward it — {self.__name__}(gl_state, ...)")
359
+ if not is_gl_thread():
360
+ return None # Render mode / Background thread - no GL here
361
+
362
+ merged = {**self._defaults, **kwargs}
363
+
364
+ # Which candidates become uniforms, per-stage.
365
+ stage_decls = [[] for _ in self.stages]
366
+ values = {}
367
+ for kw_name, val in merged.items():
368
+ if kw_name in self._skip:
369
+ continue
370
+ glsl_type = infer_glsl_type(val)
371
+ if glsl_type is None:
372
+ continue
373
+ used = False
374
+ for stage, decls in zip(self.stages, stage_decls):
375
+ if kw_name in stage.identifiers:
376
+ used = True
377
+ if kw_name not in stage.predeclared:
378
+ decls.append((kw_name, glsl_type))
379
+ if used:
380
+ values[kw_name] = val
381
+
382
+ gen = self._generated_stages(stage_decls)
383
+ self.last_generated = {stage.kind: g[0] for stage, g in zip(self.stages, gen)}
384
+ deps = tuple(g[0] for g in gen)
385
+ key = ("shader_func", self._name)
386
+
387
+ def create():
388
+ compiled = []
389
+ try:
390
+ for stage, (src, idx, n) in zip(self.stages, gen):
391
+ compiled.append(_compile_stage(stage.kind, stage.enum, src, idx, n))
392
+ except Exception:
393
+ for shader in compiled:
394
+ gl.glDeleteShader(shader)
395
+ raise
396
+ return _ProgramRecord(_link_program(compiled))
397
+
398
+ def delete(rec):
399
+ gl.glDeleteProgram(rec.program)
400
+
401
+ if deps == self._failed_deps and id(gl_state) in self._failed_states:
402
+ rec = gl_state.peek(key) # known failure FOR THIS STATE: no retry
403
+ else:
404
+ try:
405
+ rec = gl_state.get(key, create, delete, deps=deps)
406
+ self.last_error = None
407
+ self._failed_deps = None
408
+ self._failed_states.clear()
409
+ except ShaderError as e:
410
+ if str(e) != self.last_error:
411
+ print(f"[shader_func] {self._name}: {e}")
412
+ self.last_error = str(e)
413
+ if deps != self._failed_deps:
414
+ self._failed_deps = deps
415
+ self._failed_states = set()
416
+ self._failed_states.add(id(gl_state))
417
+ rec = gl_state.peek(key) # last good program, if any
418
+ if rec is None:
419
+ return None
420
+
421
+ prev_program = _scalar(gl.glGetIntegerv(gl.GL_CURRENT_PROGRAM))
422
+ gl.glUseProgram(rec.program)
423
+ try:
424
+ for uname, val in values.items():
425
+ info = rec.uniforms.get(uname)
426
+ if info is None:
427
+ continue # optimized out (or only matched a non-uniform use)
428
+ loc, utype, _size = info
429
+ target = _SAMPLER_TARGETS.get(utype)
430
+ if target is not None:
431
+ unit = rec.sampler_units.get(uname, 0)
432
+ gl.glActiveTexture(gl.GL_TEXTURE0 + unit)
433
+ if type(val).__name__ == "GLTexture":
434
+ gl.glBindTexture(val.target, val.texture_id)
435
+ else:
436
+ # Raw texture id for a predeclared sampler - bind
437
+ # to the target implied by the sampler type.
438
+ gl.glBindTexture(target, int(val))
439
+ gl.glUniform1i(loc, unit)
440
+ continue
441
+ setter = _SETTERS.get(utype)
442
+ if setter is None:
443
+ continue
444
+ try:
445
+ setter(loc, val)
446
+ except Exception as e:
447
+ print(f"[shader_func] {self._name}: setting uniform "
448
+ f"{uname!r} from {type(val).__name__} failed: {e}")
449
+
450
+ body_kwargs = dict(merged)
451
+ body_kwargs["gl_state"] = gl_state
452
+ body_kwargs["program"] = rec.program
453
+ if not self._wants_var_kwargs:
454
+ body_kwargs = {k: v for k, v in body_kwargs.items()
455
+ if k in self._body_params}
456
+ return self._func(**body_kwargs)
457
+ finally:
458
+ gl.glBindVertexArray(0)
459
+ gl.glActiveTexture(gl.GL_TEXTURE0)
460
+ gl.glUseProgram(prev_program)
461
+
462
+ def __repr__(self):
463
+ state = f"error: {self.last_error.splitlines()[0]}" if self.last_error else "ok"
464
+ return f"ShaderFunc({self._name}, {state})"
465
+
466
+
467
+ def shader_func(fragment=None, vertex=None, name=None):
468
+ """Decorator: `@shader_func(fragment=FRAG)` or
469
+ `@shader_func(fragment=FRAG, vertex=VERT)`. With no vertex source the
470
+ fullscreen-triangle vertex stage is used (body: bind an empty VAO via
471
+ gl_state.vao(key) and glDrawArrays(GL_TRIANGLES, 0, 3))."""
472
+ if callable(fragment):
473
+ raise TypeError("@shader_func needs GLSL — use @shader_func(fragment=...)")
474
+
475
+ def deco(func):
476
+ return ShaderFunc(func, fragment=fragment, vertex=vertex, name=name)
477
+
478
+ return deco
@@ -0,0 +1,45 @@
1
+ """Tensor core functions and supporting definitions."""
2
+ from meltygui.core.core_render import release_input_refs
3
+
4
+
5
+ def _voxels_cleanup(draw_state):
6
+ """Melty.cleanup hook (@render_func(on_cleanup=…)) for draw_voxels: sever
7
+ every reference this draw_state holds to the source tensor and the
8
+ uploaded volume, so the session teardown's torch.cuda.empty_cache() can
9
+ actually return the VRAM. Three places hold it:
10
+ * gl_state — the 3-D texture / CUDA-registered PBO ("volume" /
11
+ "volume_cuda") and the label atlas: release() queues them for the
12
+ GL-thread delete the teardown flushes;
13
+ * the wrapper's input slots (_input_value & co.) — the tensor the view
14
+ rendered; release_input_refs resets them;
15
+ * anything else tensor-shaped that landed on the draw_state (misc or a
16
+ plain attribute) — scrubbed generically rather than by name, so a new
17
+ field can't silently pin a volume.
18
+ The draw_state itself survives (registry entries persist across a restart-
19
+ in-place); a re-render refills everything."""
20
+ from meltygui.model.tensor_model import _is_tensorish
21
+
22
+ misc = getattr(draw_state, "misc", None)
23
+ if isinstance(misc, dict):
24
+ gl_state = misc.get("gl_state")
25
+ if gl_state is not None:
26
+ try:
27
+ gl_state.release()
28
+ except Exception as e:
29
+ print(f"[draw_voxels] gl_state release failed: {e!r}")
30
+ for k in [k for k, v in misc.items() if _is_tensorish(v)]:
31
+ misc.pop(k, None)
32
+ release_input_refs(draw_state)
33
+ for k, v in list(vars(draw_state).items()):
34
+ if k != "misc" and _is_tensorish(v):
35
+ setattr(draw_state, k, None)
36
+
37
+
38
+ def source_identity(src):
39
+ """Cache identity of a tensor source: id + in-place version + the live
40
+ publish generation (live_view._stamp_publish_gen). id()/_version alone
41
+ collide across runs — the released previous generation's address is
42
+ reused by the next run's tensor, _version 0 on both — and the cached
43
+ texture of the OLD run was served for the new value."""
44
+ from meltygui.code.live_view import publish_gen
45
+ return (id(src), getattr(src, "_version", 0), publish_gen(src))