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,824 @@
1
+ """CUDA raymarcher that reads a torch tensor IN PLACE — the experimental
2
+ "compute" sibling of voxel_view's GL fragment-shader path.
3
+
4
+ The GL path has to own a 3-D texture, which means every volume is a COPY of
5
+ the tensor (dtype-coerced, made contiguous, neural-flow repacked, uploaded)
6
+ — and a live tensor whose values or shape change pays that whole-tensor pass
7
+ per change. This module flips the data flow: the kernel runs ON THE TENSOR'S
8
+ GPU and samples the tensor's memory directly through its strides, so
9
+
10
+ * a slice / axis remap is just a torch VIEW (data_ptr + strides, no copy),
11
+ * any dtype is decoded in the load (bf16, fp16, ints, bool, f64 — no f32 copy),
12
+ * neural flow is index math inside the sampler,
13
+ * a shape change is new kernel args,
14
+ * the only thing that ever crosses GPUs is the finished 2-D image (a few
15
+ MB), so the tensor may live on ANY device — a 50 GB H100 tensor renders
16
+ where it is and ships ~9 MB to the display GPU.
17
+
18
+ Cost: nearest sampling only, and a linear strided buffer has none of a 3-D
19
+ texture's swizzled locality — per-sample reads are slower than the texture
20
+ path. Worth it exactly when the data moves.
21
+
22
+ Mechanics: PyCUDA SourceModule, compiled once per DEVICE (its primary
23
+ context is retained and pushed around the launch only — never around GL
24
+ calls, see core/graphics/cuda_context_core.py), launched on the legacy default
25
+ stream, which orders after torch's default-stream producers without an
26
+ explicit sync. The output image is a torch float16 (H, W, 4) tensor on the
27
+ tensor's device (premultiplied LINEAR RGBA, the same light the GL pass writes
28
+ into the fp16 scene — values above 1 are HDR, negatives are P3); the
29
+ caller moves it to wherever it's displayed.
30
+ """
31
+
32
+ import math
33
+
34
+ import numpy as np
35
+ from meltygui.core.graphics.cuda_context_core import using_device
36
+
37
+ from meltygui.core.graphics.cuda_kernel_core import kernel_functions
38
+ from meltygui.model.cuda_tensor_model import CUDA_LOAD_SOURCE, dtype_code
39
+
40
+ KERNEL = CUDA_LOAD_SOURCE + r"""struct Vol {
41
+ const unsigned char* data; int dtype;
42
+ int nz, ny, nx; // DISPLAYED extents (after neural flow)
43
+ int snz, sny, snx; // source view extents
44
+ long long sz, sy, sx; // source view element strides
45
+ int nf_chop, nf_along, nf_chunk; // -1 = off; axes 0=z 1=y 2=x
46
+ float nlo, nhi; int norm_mode; // 0 off, 1 (v-lo)/(hi-lo), 2 v/maxabs
47
+ };
48
+
49
+ // Shading parameters, one float array (see shade_params() in cuda_march.py)
50
+ struct Shade {
51
+ bool draw_plane, draw_shading, self_shading;
52
+ float shadow_opacity, shadow_softness, plane_side;
53
+ float3 shadow_tint, light_pos, light_tint;
54
+ float light_brightness, ambient_light, shading_strength;
55
+ };
56
+
57
+ // ── float3 helpers ──
58
+ __device__ __forceinline__ float3 f3(float x, float y, float z) { return make_float3(x, y, z); }
59
+ __device__ __forceinline__ float3 add3(float3 a, float3 b) { return f3(a.x+b.x, a.y+b.y, a.z+b.z); }
60
+ __device__ __forceinline__ float3 sub3(float3 a, float3 b) { return f3(a.x-b.x, a.y-b.y, a.z-b.z); }
61
+ __device__ __forceinline__ float3 mul3(float3 a, float s) { return f3(a.x*s, a.y*s, a.z*s); }
62
+ __device__ __forceinline__ float3 div3(float3 a, float3 b) { return f3(a.x/b.x, a.y/b.y, a.z/b.z); }
63
+ __device__ __forceinline__ float dot3(float3 a, float3 b) { return a.x*b.x + a.y*b.y + a.z*b.z; }
64
+ __device__ __forceinline__ float len3(float3 a) { return sqrtf(dot3(a, a)); }
65
+ __device__ __forceinline__ float3 norm3(float3 a) { float l = len3(a); return l > 0.f ? mul3(a, 1.f/l) : a; }
66
+ __device__ __forceinline__ float clamp01(float x) { return fminf(fmaxf(x, 0.f), 1.f); }
67
+ // Extended-sRGB decode (hdr_color.py): the sRGB curve mirrored for negatives,
68
+ // no ceiling — a LUT entry above 1 is brighter than the desktop's white, a
69
+ // negative one is outside the sRGB gamut (P3). powf alone made NaNs of those.
70
+ __device__ __forceinline__ float srgb_dec(float x) { return copysignf(powf(fabsf(x), 2.2f), x); }
71
+
72
+ // Slab test against the box [-b, +b]: (t_enter, t_exit).
73
+ __device__ __forceinline__ float2 rayBox(float3 ro, float3 rd, float3 b) {
74
+ float3 inv = f3(1.0f / rd.x, 1.0f / rd.y, 1.0f / rd.z);
75
+ float t0x = (-b.x - ro.x) * inv.x, t1x = (b.x - ro.x) * inv.x;
76
+ float t0y = (-b.y - ro.y) * inv.y, t1y = (b.y - ro.y) * inv.y;
77
+ float t0z = (-b.z - ro.z) * inv.z, t1z = (b.z - ro.z) * inv.z;
78
+ return make_float2(fmaxf(fmaxf(fminf(t0x, t1x), fminf(t0y, t1y)), fminf(t0z, t1z)),
79
+ fminf(fminf(fmaxf(t0x, t1x), fmaxf(t0y, t1y)), fmaxf(t0z, t1z)));
80
+ }
81
+
82
+ // Raw value at DISPLAY voxel (iz, iy, ix) — neural-flow remap + strided load
83
+ // + normalize. Indices must already be in range.
84
+ __device__ __forceinline__ float sample_i(const Vol& v, int iz, int iy, int ix) {
85
+ int d[3] = {iz, iy, ix};
86
+ if (v.nf_chop >= 0) {
87
+ // cat(split(chop, chunk), along): display along-index a' = j*A + i
88
+ // (block j, original i); display chop-index c' -> source j*chunk + c'.
89
+ int A = (v.nf_along == 0) ? v.snz : ((v.nf_along == 1) ? v.sny : v.snx);
90
+ int j = d[v.nf_along] / A;
91
+ d[v.nf_along] -= j * A;
92
+ d[v.nf_chop] += j * v.nf_chunk;
93
+ }
94
+ if (d[0] >= v.snz || d[1] >= v.sny || d[2] >= v.snx) return 0.0f; // pad
95
+ long long e = (long long)d[0] * v.sz + (long long)d[1] * v.sy
96
+ + (long long)d[2] * v.sx;
97
+ float x = load_at(v.data, v.dtype, e);
98
+ if (v.norm_mode == 1) x = (x - v.nlo) / (v.nhi - v.nlo + 1e-12f);
99
+ else if (v.norm_mode == 2) x = x / (v.nhi + 1e-12f);
100
+ return x;
101
+ }
102
+
103
+ // Nearest sample at texcoord p in [0,1]^3 (x <-> width, y <-> height, z <-> depth)
104
+ // — the GL path's texture(volume, p).r with GL_NEAREST.
105
+ __device__ __forceinline__ float sample(const Vol& v, float3 p) {
106
+ int ix = min(max((int)floorf(p.x * v.nx), 0), v.nx - 1);
107
+ int iy = min(max((int)floorf(p.y * v.ny), 0), v.ny - 1);
108
+ int iz = min(max((int)floorf(p.z * v.nz), 0), v.nz - 1);
109
+ return sample_i(v, iz, iy, ix);
110
+ }
111
+
112
+ // Trilinear sample (GL's linear sampler, texel centers at (i+0.5)/n, edge
113
+ // clamped) — used by the SHADING reads only: normals and the light marches
114
+ // want a smooth field regardless of the colour march's nearest toggle.
115
+ // Reads the shading MIP when present (the fast path), else the source.
116
+
117
+ // The shared transfer function: raw sample -> (v: LUT coordinate, m: opacity
118
+ // drive). Contrast about mid-grey then brightness; `centered` maps signed data
119
+ // so raw 0 sits at the LUT middle and opacity keys on magnitude.
120
+ __device__ __forceinline__ float2 remapValue(float val, float brightness, float contrast, bool centered) {
121
+ if (centered) val = val * 0.5f + 0.5f;
122
+ val = (val - 0.5f) * contrast + 0.5f;
123
+ float m;
124
+ if (centered) {
125
+ val = clamp01(0.5f + (val - 0.5f) * brightness);
126
+ m = fabsf(val - 0.5f) * 2.0f;
127
+ } else {
128
+ val = clamp01(val * brightness);
129
+ m = val;
130
+ }
131
+ return make_float2(val, m);
132
+ }
133
+
134
+ // The opacity ramp: at/above the gate fully opaque (hard isosurface), below it
135
+ // (m/gate)^4 scaled by density and the volume-NORMALIZED segment length.
136
+ __device__ __forceinline__ float alphaFor(float m, float seg_n, float gate, float density) {
137
+ if (m >= gate) return 1.0f;
138
+ float r = m / gate; r *= r; r *= r;
139
+ return clamp01(r * density * seg_n * 50.0f);
140
+ }
141
+
142
+ struct Ctx {
143
+ Vol v; float3 vs; float gate, density, brightness, contrast; bool centered;
144
+ // Shading OPACITY mip: a small DENSE f16 x2 grid over the display
145
+ // volume, each cell (f, k) of its block under the CURRENT transfer:
146
+ // f = fraction of voxels at/above the opacity gate (hard occluders),
147
+ // k = mean sub-gate opacity density (r^4 * density * 50, per unit
148
+ // seg_n). The light march reconstructs a step of L physical voxels as
149
+ // T = ((1-f)(1-k*s_vox))^L — exact for uniform haze, solid blocks,
150
+ // and sparse spikes along ANY axis — so a coarse cell transmits the
151
+ // SAME light as the high-res voxels it replaces, where value-space
152
+ // estimators (avg/RMS/max) made blocky cells shade darker than the
153
+ // data they approximate. Null = read the source directly.
154
+ const __half* mip; int mz, my, mx;
155
+ };
156
+
157
+ __device__ __forceinline__ float2 mip_at(const Ctx& c, int iz, int iy, int ix) {
158
+ long long e = (((long long)iz * c.my + iy) * c.mx + ix) * 2;
159
+ return make_float2(__half2float(c.mip[e]), __half2float(c.mip[e + 1]));
160
+ }
161
+
162
+ // Trilinear over the (f, k) opacity mip — both channels in one pass.
163
+ __device__ __forceinline__ float2 sample_mip(const Ctx& c, float3 p) {
164
+ float fx = p.x * c.mx - 0.5f, fy = p.y * c.my - 0.5f, fz = p.z * c.mz - 0.5f;
165
+ int x0 = (int)floorf(fx), y0 = (int)floorf(fy), z0 = (int)floorf(fz);
166
+ float tx = fx - x0, ty = fy - y0, tz = fz - z0;
167
+ int x1 = min(max(x0 + 1, 0), c.mx - 1), y1 = min(max(y0 + 1, 0), c.my - 1), z1 = min(max(z0 + 1, 0), c.mz - 1);
168
+ x0 = min(max(x0, 0), c.mx - 1); y0 = min(max(y0, 0), c.my - 1); z0 = min(max(z0, 0), c.mz - 1);
169
+ float2 acc = make_float2(0.f, 0.f);
170
+ float wz[2] = {1 - tz, tz}; float wy[2] = {1 - ty, ty}; float wx[2] = {1 - tx, tx};
171
+ int zs[2] = {z0, z1}; int ys[2] = {y0, y1}; int xs[2] = {x0, x1};
172
+ for (int a = 0; a < 2; a++)
173
+ for (int b = 0; b < 2; b++)
174
+ for (int d = 0; d < 2; d++) {
175
+ float w = wz[a] * wy[b] * wx[d];
176
+ float2 v = mip_at(c, zs[a], ys[b], xs[d]);
177
+ acc.x += w * v.x; acc.y += w * v.y;
178
+ }
179
+ return acc;
180
+ }
181
+
182
+ // Full-res software trilinear on the SOURCE (the no-mip fallback).
183
+ __device__ __forceinline__ float sample_src_lin(const Ctx& c, float3 p) {
184
+ int nx = c.v.nx, ny = c.v.ny, nz = c.v.nz;
185
+ float fx = p.x * nx - 0.5f, fy = p.y * ny - 0.5f, fz = p.z * nz - 0.5f;
186
+ int x0 = (int)floorf(fx), y0 = (int)floorf(fy), z0 = (int)floorf(fz);
187
+ float tx = fx - x0, ty = fy - y0, tz = fz - z0;
188
+ int x1 = min(max(x0 + 1, 0), nx - 1), y1 = min(max(y0 + 1, 0), ny - 1), z1 = min(max(z0 + 1, 0), nz - 1);
189
+ x0 = min(max(x0, 0), nx - 1); y0 = min(max(y0, 0), ny - 1); z0 = min(max(z0, 0), nz - 1);
190
+ float c00 = sample_i(c.v, z0, y0, x0) * (1 - tx) + sample_i(c.v, z0, y0, x1) * tx;
191
+ float c10 = sample_i(c.v, z0, y1, x0) * (1 - tx) + sample_i(c.v, z0, y1, x1) * tx;
192
+ float c01 = sample_i(c.v, z1, y0, x0) * (1 - tx) + sample_i(c.v, z1, y0, x1) * tx;
193
+ float c11 = sample_i(c.v, z1, y1, x0) * (1 - tx) + sample_i(c.v, z1, y1, x1) * tx;
194
+ float c0 = c00 * (1 - ty) + c10 * ty, c1 = c01 * (1 - ty) + c11 * ty;
195
+ return c0 * (1 - tz) + c1 * tz;
196
+ }
197
+
198
+
199
+ // Transmittance from world point p toward light lp: a COARSE fixed-count
200
+ // march of the same transfer function (linear reads), multiplying out
201
+ // per-step opacity. Returns (T, t_occ): t_occ = distance along the light ray
202
+ // where T first dropped below 0.5 (the occluder height driving the ground
203
+ // penumbra); non-occluding rays report their mid-chord, box misses 0.
204
+ __device__ float2 lightVisibilityInfo(const Ctx& c, float3 p, float3 lp, int steps, float max_dist) {
205
+ float3 ld = norm3(sub3(lp, p));
206
+ float2 span = rayBox(p, ld, c.vs);
207
+ float t0 = fmaxf(span.x, 0.0f);
208
+ float t1 = fminf(fminf(span.y, len3(sub3(lp, p))), max_dist);
209
+ if (t0 >= t1) return make_float2(1.0f, 0.0f);
210
+ float ss = (t1 - t0) / (float)steps;
211
+ float seg_n = ss * len3(div3(ld, c.vs));
212
+ float T = 1.0f, t_occ = 0.0f, t = t0 + ss * 0.5f;
213
+ // With the (f, k) opacity mip, per PHYSICAL VOXEL crossed (voxels are
214
+ // world-space cubes, so voxels-per-step is direction-independent):
215
+ // hard occluders hit with probability f, sub-gate haze contributes
216
+ // k * s_vox where s_vox = the step's seg_n split over its L voxels —
217
+ // keeping the fraction-of-volume opacity semantics direction-aware
218
+ // (crossing a 4-voxel axis is 4 chances, not 512). T over the step is
219
+ // ((1-f)(1-k*s_vox))^L: exact for uniform haze, solid blocks, and
220
+ // sparse spikes. Without a mip: the full-res value path.
221
+ float L = fmaxf(ss * 0.5f * (float)max(c.v.nx, max(c.v.ny, c.v.nz)), 1e-6f);
222
+ float s_vox = seg_n / L;
223
+ for (int i = 0; i < steps; i++) {
224
+ float3 q = add3(mul3(div3(add3(p, mul3(ld, t)), c.vs), 0.5f), f3(0.5f, 0.5f, 0.5f));
225
+ float a;
226
+ if (c.mip) {
227
+ float2 fk = sample_mip(c, q);
228
+ float f = fminf(fmaxf(fk.x, 0.0f), 1.0f);
229
+ float a_h = fminf(fmaxf(fk.y, 0.0f) * s_vox, 1.0f);
230
+ float t_vox = (1.0f - f) * (1.0f - a_h);
231
+ a = 1.0f - powf(fmaxf(t_vox, 0.0f), L);
232
+ } else {
233
+ float m = remapValue(sample_src_lin(c, q), c.brightness, c.contrast, c.centered).y;
234
+ a = alphaFor(m, seg_n, c.gate, c.density);
235
+ }
236
+ T *= 1.0f - a;
237
+ if (t_occ == 0.0f && T < 0.5f) t_occ = t;
238
+ if (T < 0.02f) break;
239
+ t += ss;
240
+ }
241
+ if (t_occ == 0.0f) t_occ = 0.5f * (t0 + t1);
242
+ return make_float2(T, t_occ);
243
+ }
244
+
245
+ // Baked floor-shadow map lookup: (vis, t_occ) at floor point (x, y),
246
+ // bilinear, edge texels clamped; outside the map's [-R, R] square = fully
247
+ // lit. The map is baked at FULL RES (no mip) once per volume version.
248
+ __device__ __forceinline__ float2 floor_lookup(const __half* __restrict__ m,
249
+ int fw, int fh, float Rx, float Ry,
250
+ float x, float y) {
251
+ if (x < -Rx || x > Rx || y < -Ry || y > Ry) return make_float2(1.0f, 0.0f);
252
+ float u = (x + Rx) / (2.0f * Rx) * fw - 0.5f;
253
+ float v = (y + Ry) / (2.0f * Ry) * fh - 0.5f;
254
+ int x0 = (int)floorf(u), y0 = (int)floorf(v);
255
+ float tx = u - x0, ty = v - y0;
256
+ int x1 = min(max(x0 + 1, 0), fw - 1), y1 = min(max(y0 + 1, 0), fh - 1);
257
+ x0 = min(max(x0, 0), fw - 1); y0 = min(max(y0, 0), fh - 1);
258
+ #define FM(yy, xx, ch) __half2float(m[((long long)(yy) * fw + (xx)) * 2 + (ch)])
259
+ float v00 = FM(y0, x0, 0), v10 = FM(y0, x1, 0), v01 = FM(y1, x0, 0), v11 = FM(y1, x1, 0);
260
+ float t00 = FM(y0, x0, 1), t10 = FM(y0, x1, 1), t01 = FM(y1, x0, 1), t11 = FM(y1, x1, 1);
261
+ #undef FM
262
+ float a = (v00 * (1 - tx) + v10 * tx) * (1 - ty) + (v01 * (1 - tx) + v11 * tx) * ty;
263
+ float b = (t00 * (1 - tx) + t10 * tx) * (1 - ty) + (t01 * (1 - tx) + t11 * tx) * ty;
264
+ return make_float2(a, b);
265
+ }
266
+
267
+ // ── fine DDA (Amanatides-Woo) over display voxels in [t0, t1): each voxel
268
+ // along the ray is visited EXACTLY ONCE with its exact segment length —
269
+ // there is no step size. Zoomed out, no voxel is skipped (fixed steps
270
+ // aliased past sub-step spikes); zoomed in, a screen-filling voxel costs
271
+ // one sample instead of step_size's fifty, and nothing far-clips at
272
+ // max_steps * step_size. `budget` (shared across cells) is the watchdog.
273
+ // Returns false when the walk should stop (opacity saturated / budget out).
274
+ __device__ bool dda_fine(const Ctx& c, const Shade& S,
275
+ const float* __restrict__ lut, int lut_n,
276
+ float3 ro, float3 rd, float t0, float t1,
277
+ float dirn, float view_cos,
278
+ float4& acc, int& budget, int K)
279
+ {
280
+ if (t1 <= t0) return true;
281
+ int nx = c.v.nx, ny = c.v.ny, nz = c.v.nz;
282
+ // index-space ray: u_a(t) = ((ro_a/vs_a)*0.5 + 0.5)*n_a + rd_a*0.5*n_a/vs_a * t
283
+ float ox = ((ro.x / c.vs.x) * 0.5f + 0.5f) * nx, dx = rd.x * 0.5f * nx / c.vs.x;
284
+ float oy = ((ro.y / c.vs.y) * 0.5f + 0.5f) * ny, dy = rd.y * 0.5f * ny / c.vs.y;
285
+ float oz = ((ro.z / c.vs.z) * 0.5f + 0.5f) * nz, dz = rd.z * 0.5f * nz / c.vs.z;
286
+ float tn = t0 + (t1 - t0) * 1e-6f; // nudge off the entry face
287
+ int ix = min(max((int)floorf(ox + dx * tn), 0), nx - 1);
288
+ int iy = min(max((int)floorf(oy + dy * tn), 0), ny - 1);
289
+ int iz = min(max((int)floorf(oz + dz * tn), 0), nz - 1);
290
+ int sx = dx > 0.f ? 1 : -1, sy = dy > 0.f ? 1 : -1, sz2 = dz > 0.f ? 1 : -1;
291
+ float BIG = 1e30f;
292
+ float tDx = dx != 0.f ? fabsf(1.0f / dx) : BIG;
293
+ float tDy = dy != 0.f ? fabsf(1.0f / dy) : BIG;
294
+ float tDz = dz != 0.f ? fabsf(1.0f / dz) : BIG;
295
+ float tMx = dx != 0.f ? ((float)(ix + (sx > 0 ? 1 : 0)) - ox) / dx : BIG;
296
+ float tMy = dy != 0.f ? ((float)(iy + (sy > 0 ? 1 : 0)) - oy) / dy : BIG;
297
+ float tMz = dz != 0.f ? ((float)(iz + (sz2 > 0 ? 1 : 0)) - oz) / dz : BIG;
298
+ float tm = t0;
299
+ while (true) {
300
+ // K > 1 = the quality dial: coalesce up to K voxel crossings into
301
+ // one sample (taken at the segment midpoint's voxel). K == 1 is
302
+ // the exact walk and uses the tracked indices (bit-stable at
303
+ // boundaries, which the coarse-skip equivalence relies on).
304
+ float tExit = fminf(fminf(tMx, tMy), fminf(tMz, t1));
305
+ for (int k2 = 1; k2 < K && tExit < t1; k2++) {
306
+ if (tMx <= tMy && tMx <= tMz) {
307
+ ix += sx; tMx += tDx;
308
+ if (ix < 0 || ix >= nx) break;
309
+ } else if (tMy <= tMz) {
310
+ iy += sy; tMy += tDy;
311
+ if (iy < 0 || iy >= ny) break;
312
+ } else {
313
+ iz += sz2; tMz += tDz;
314
+ if (iz < 0 || iz >= nz) break;
315
+ }
316
+ tExit = fminf(fminf(tMx, tMy), fminf(tMz, t1));
317
+ }
318
+ float seg = tExit - tm;
319
+ if (seg > 0.0f) {
320
+ if (--budget < 0) return false;
321
+ int jx = ix, jy = iy, jz = iz;
322
+ if (K > 1) {
323
+ float tc = tm + seg * 0.5f;
324
+ jx = min(max((int)floorf(ox + dx * tc), 0), nx - 1);
325
+ jy = min(max((int)floorf(oy + dy * tc), 0), ny - 1);
326
+ jz = min(max((int)floorf(oz + dz * tc), 0), nz - 1);
327
+ }
328
+ float2 vm = remapValue(sample_i(c.v, jz, jy, jx),
329
+ c.brightness, c.contrast, c.centered);
330
+ float seg_n = seg * dirn * view_cos;
331
+ float a = alphaFor(vm.y, seg_n, c.gate, c.density);
332
+ if (a > 0.0f) {
333
+ float f = fminf(fmaxf(vm.x * (float)lut_n - 0.5f, 0.0f), (float)(lut_n - 1));
334
+ int i0 = (int)floorf(f); int i1 = min(i0 + 1, lut_n - 1); float wl = f - (float)i0;
335
+ float3 col = f3(srgb_dec(lut[3*i0] * (1.f - wl) + lut[3*i1] * wl),
336
+ srgb_dec(lut[3*i0+1] * (1.f - wl) + lut[3*i1+1] * wl),
337
+ srgb_dec(lut[3*i0+2] * (1.f - wl) + lut[3*i1+2] * wl));
338
+ // Voxels keep their LUT colours — no normal-based Lambert;
339
+ // self_shading is a pure transmittance darkening.
340
+ if (S.draw_shading && S.self_shading && a > 0.01f) {
341
+ float3 wp = add3(ro, mul3(rd, tm + seg * 0.5f));
342
+ float vis = lightVisibilityInfo(c, wp, S.light_pos, 6, 0.7f).x;
343
+ float lit = S.ambient_light + (1.0f - S.ambient_light) * vis;
344
+ float shade = 1.0f + (lit - 1.0f) * S.shading_strength;
345
+ col = mul3(col, shade);
346
+ }
347
+ float kk = (1.0f - acc.w) * a;
348
+ acc.x += kk * col.x; acc.y += kk * col.y; acc.z += kk * col.z;
349
+ acc.w += (1.0f - acc.w) * a;
350
+ if (acc.w > 0.98f) return false;
351
+ }
352
+ }
353
+ if (tExit >= t1) return true;
354
+ if (tMx <= tMy && tMx <= tMz) {
355
+ ix += sx; tm = tMx; tMx += tDx;
356
+ if (ix < 0 || ix >= nx) return true;
357
+ } else if (tMy <= tMz) {
358
+ iy += sy; tm = tMy; tMy += tDy;
359
+ if (iy < 0 || iy >= ny) return true;
360
+ } else {
361
+ iz += sz2; tm = tMz; tMz += tDz;
362
+ if (iz < 0 || iz >= nz) return true;
363
+ }
364
+ }
365
+ }
366
+
367
+ extern "C" __global__ void march(
368
+ const unsigned char* __restrict__ data, int dtype,
369
+ int nz, int ny, int nx, int snz, int sny, int snx,
370
+ long long sz, long long sy, long long sx,
371
+ int nf_chop, int nf_along, int nf_chunk,
372
+ float nlo, float nhi, int norm_mode,
373
+ const float* __restrict__ lut, int lut_n,
374
+ __half* __restrict__ out, int W, int H,
375
+ float tilt, float spin, float roll, float zoom, float pan_x, float pan_y, float pan_z,
376
+ int ortho, float aspect, float vsx, float vsy, float vsz,
377
+ float step_size, int max_steps, float density, float threshold,
378
+ float brightness, float contrast, float gamma, int centered,
379
+ const float* __restrict__ sh,
380
+ const __half* __restrict__ mipd, int mz, int my, int mx,
381
+ const __half* __restrict__ fmap, int fw, int fh, float fRx, float fRy)
382
+ {
383
+ int px = blockIdx.x * blockDim.x + threadIdx.x;
384
+ int py = blockIdx.y * blockDim.y + threadIdx.y;
385
+ if (px >= W || py >= H) return;
386
+ Ctx c;
387
+ c.v = Vol{data, dtype, nz, ny, nx, snz, sny, snx, sz, sy, sx,
388
+ nf_chop, nf_along, nf_chunk, nlo, nhi, norm_mode};
389
+ c.vs = f3(vsx, vsy, vsz);
390
+ c.gate = 1.0f - fminf(fmaxf(threshold, 0.0f), 0.999f);
391
+ c.density = density; c.brightness = brightness; c.contrast = contrast;
392
+ c.centered = centered != 0;
393
+ c.mip = mipd; c.mz = mz; c.my = my; c.mx = mx;
394
+ Shade S;
395
+ S.draw_plane = sh[0] > 0.5f; S.shadow_opacity = sh[1]; S.shadow_softness = sh[2];
396
+ S.shadow_tint = f3(sh[3], sh[4], sh[5]); S.plane_side = sh[6];
397
+ S.draw_shading = sh[7] > 0.5f; S.self_shading = sh[8] > 0.5f;
398
+ S.light_pos = f3(sh[9], sh[10], sh[11]); S.light_tint = f3(sh[12], sh[13], sh[14]);
399
+ S.light_brightness = sh[15]; S.ambient_light = sh[16]; S.shading_strength = sh[17];
400
+
401
+ // ── the GL pass's z-up orbit camera, verbatim ──
402
+ float u = ((float)px + 0.5f) / (float)W, w = ((float)py + 0.5f) / (float)H;
403
+ float ct = cosf(tilt), st = sinf(tilt), cs = cosf(spin), ss = sinf(spin);
404
+ float3 fwd = f3(-cs * ct, -ss * ct, -st);
405
+ float3 right0 = f3(-ss, cs, 0.0f);
406
+ float3 up0 = f3(right0.y * fwd.z - right0.z * fwd.y,
407
+ right0.z * fwd.x - right0.x * fwd.z,
408
+ right0.x * fwd.y - right0.y * fwd.x);
409
+ // roll turns right toward up about the view axis (voxel_camera.basis)
410
+ float cr = cosf(roll), sr = sinf(roll);
411
+ float3 right = f3(right0.x * cr + up0.x * sr, right0.y * cr + up0.y * sr,
412
+ right0.z * cr + up0.z * sr);
413
+ float3 up = f3(right.y * fwd.z - right.z * fwd.y,
414
+ right.z * fwd.x - right.x * fwd.z,
415
+ right.x * fwd.y - right.y * fwd.x);
416
+ float3 eye = sub3(f3(pan_x, pan_y, pan_z), mul3(fwd, zoom));
417
+ float ndx = (u * 2.0f - 1.0f) * aspect, ndy = w * 2.0f - 1.0f;
418
+ float3 ro, rd;
419
+ if (ortho) {
420
+ ro = add3(eye, mul3(add3(mul3(right, ndx), mul3(up, ndy)), zoom / 1.7f));
421
+ rd = fwd;
422
+ } else {
423
+ ro = eye;
424
+ rd = norm3(add3(mul3(fwd, 1.7f), add3(mul3(right, ndx), mul3(up, ndy))));
425
+ }
426
+ float view_cos = dot3(rd, fwd);
427
+
428
+ // ── shadow catcher: the plane the box rests on (z = -vs.z, mirrored by
429
+ // plane_side) is INVISIBLE — only the volume's cast shadow composites,
430
+ // as a darkening with alpha = blocked light. One-sided. ──
431
+ float plane_t = -1.0f, plane_a = 0.0f;
432
+ float3 plane_c = S.shadow_tint;
433
+ if (S.draw_plane && S.draw_shading && rd.z * S.plane_side < -1e-6f
434
+ && ro.z * S.plane_side > -c.vs.z) {
435
+ plane_t = (-c.vs.z * S.plane_side - ro.z) / rd.z;
436
+ float3 pw = add3(ro, mul3(rd, plane_t));
437
+ float ext = fmaxf(c.vs.x, c.vs.y);
438
+ float r = fmaxf(sqrtf(pw.x * pw.x + pw.y * pw.y) - ext * 1.1f, 0.0f);
439
+ float3 pl_light = f3(S.light_pos.x, S.light_pos.y, S.light_pos.z * S.plane_side);
440
+ // Baked map when present (full-res, computed once per volume
441
+ // version): (vis, t_occ) per floor texel, blur = 4 more map reads.
442
+ float2 vi = fmap ? floor_lookup(fmap, fw, fh, fRx, fRy, pw.x, pw.y)
443
+ : lightVisibilityInfo(c, pw, pl_light, 24, 1e8f);
444
+ float vis = vi.x;
445
+ float blur_r = S.shadow_softness * vi.y;
446
+ if (blur_r > 1e-4f) {
447
+ float acc_v = vis;
448
+ for (int k = 0; k < 4; k++) {
449
+ float ang = (float)k * 1.5707963f + 0.7853982f;
450
+ float3 op = add3(pw, mul3(f3(cosf(ang), sinf(ang), 0.0f), blur_r));
451
+ acc_v += fmap ? floor_lookup(fmap, fw, fh, fRx, fRy, op.x, op.y).x
452
+ : lightVisibilityInfo(c, op, pl_light, 10, 1e8f).x;
453
+ }
454
+ vis = acc_v / 5.0f;
455
+ }
456
+ float shadow = (1.0f - S.ambient_light) * (1.0f - vis);
457
+ plane_a = clamp01(shadow * S.shadow_opacity) * expf(-1.5f * r / ext);
458
+ }
459
+
460
+ float2 hit = rayBox(ro, rd, c.vs);
461
+ bool box_hit = !(hit.x > hit.y || hit.y < 0.0f);
462
+ float4 acc = make_float4(0.f, 0.f, 0.f, 0.f);
463
+ if (box_hit || plane_t > 0.0f) {
464
+ // Plane in FRONT of the volume: composite it first.
465
+ if (plane_t > 0.0f && box_hit && plane_t <= fmaxf(hit.x, 0.0f)) {
466
+ acc = make_float4(plane_c.x * plane_a, plane_c.y * plane_a, plane_c.z * plane_a, plane_a);
467
+ plane_t = -1.0f;
468
+ }
469
+ if (box_hit) {
470
+ // Two-level DDA: outer over the (f, k) mip cells (empty cells
471
+ // skipped in O(1)), inner exact voxel traversal. No step size —
472
+ // `step_size` is a no-op for the CUDA colour march; max_steps
473
+ // is the voxel-visit watchdog. Tiny volumes (or no mip) go
474
+ // straight to fine DDA.
475
+ float t0v = fmaxf(hit.x, 0.0f);
476
+ float t1v = hit.y;
477
+ float dirn = len3(div3(rd, c.vs));
478
+ int budget = max_steps;
479
+ // step_size's CUDA meaning: the sampling stride, floored at one
480
+ // voxel — K = how many voxel crossings coalesce per sample.
481
+ // The default (0.0005) keeps K = 1 (exact) for anything up to
482
+ // ~4000 voxels wide; raising it trades exactness for speed.
483
+ float vox_w = 2.0f / (float)max(c.v.nx, max(c.v.ny, c.v.nz));
484
+ int K = max(1, (int)(step_size / vox_w + 0.5f));
485
+ bool coarse = c.mip && ((long long)c.v.nx * c.v.ny * c.v.nz > 262144);
486
+ if (!coarse) {
487
+ dda_fine(c, S, lut, lut_n, ro, rd, t0v, t1v, dirn, view_cos, acc, budget, K);
488
+ } else {
489
+ int mx = c.mx, my = c.my, mz = c.mz;
490
+ float ox = ((ro.x / c.vs.x) * 0.5f + 0.5f) * mx, dx = rd.x * 0.5f * mx / c.vs.x;
491
+ float oy = ((ro.y / c.vs.y) * 0.5f + 0.5f) * my, dy = rd.y * 0.5f * my / c.vs.y;
492
+ float oz = ((ro.z / c.vs.z) * 0.5f + 0.5f) * mz, dz = rd.z * 0.5f * mz / c.vs.z;
493
+ float tn = t0v + (t1v - t0v) * 1e-6f;
494
+ int ix = min(max((int)floorf(ox + dx * tn), 0), mx - 1);
495
+ int iy = min(max((int)floorf(oy + dy * tn), 0), my - 1);
496
+ int iz = min(max((int)floorf(oz + dz * tn), 0), mz - 1);
497
+ int sx = dx > 0.f ? 1 : -1, sy = dy > 0.f ? 1 : -1, sz2 = dz > 0.f ? 1 : -1;
498
+ float BIG = 1e30f;
499
+ float tDx = dx != 0.f ? fabsf(1.0f / dx) : BIG;
500
+ float tDy = dy != 0.f ? fabsf(1.0f / dy) : BIG;
501
+ float tDz = dz != 0.f ? fabsf(1.0f / dz) : BIG;
502
+ float tMx = dx != 0.f ? ((float)(ix + (sx > 0 ? 1 : 0)) - ox) / dx : BIG;
503
+ float tMy = dy != 0.f ? ((float)(iy + (sy > 0 ? 1 : 0)) - oy) / dy : BIG;
504
+ float tMz = dz != 0.f ? ((float)(iz + (sz2 > 0 ? 1 : 0)) - oz) / dz : BIG;
505
+ float tm = t0v;
506
+ while (true) {
507
+ float tExit = fminf(fminf(tMx, tMy), fminf(tMz, t1v));
508
+ if (tExit > tm) {
509
+ float2 fk = mip_at(c, iz, iy, ix);
510
+ if (fk.x > 0.0f || fk.y > 1e-3f) {
511
+ if (!dda_fine(c, S, lut, lut_n, ro, rd, tm, tExit,
512
+ dirn, view_cos, acc, budget, K))
513
+ break;
514
+ }
515
+ }
516
+ if (tExit >= t1v) break;
517
+ if (tMx <= tMy && tMx <= tMz) {
518
+ ix += sx; tm = tMx; tMx += tDx;
519
+ if (ix < 0 || ix >= mx) break;
520
+ } else if (tMy <= tMz) {
521
+ iy += sy; tm = tMy; tMy += tDy;
522
+ if (iy < 0 || iy >= my) break;
523
+ } else {
524
+ iz += sz2; tm = tMz; tMz += tDz;
525
+ if (iz < 0 || iz >= mz) break;
526
+ }
527
+ }
528
+ }
529
+ }
530
+ // Plane BEHIND the volume (the usual case): composite it under.
531
+ if (plane_t > 0.0f) {
532
+ float kk = (1.0f - acc.w) * plane_a;
533
+ acc.x += kk * plane_c.x; acc.y += kk * plane_c.y; acc.z += kk * plane_c.z;
534
+ acc.w += (1.0f - acc.w) * plane_a;
535
+ }
536
+ }
537
+ // Linear premultiplied fp16, exactly what the GL pass hands the fp16
538
+ // scene: `gamma` is the same artistic curve on linear light (1.0 =
539
+ // untouched), no sRGB encode (the presentation pass does that once),
540
+ // no clamp — HDR headroom rides through — and no dither (fp16 doesn't
541
+ // band).
542
+ // The curve is mirrored for negatives (P3 rides as negative scRGB).
543
+ __half* o = out + ((long long)py * W + px) * 4;
544
+ o[0] = __float2half(copysignf(powf(fabsf(acc.x), gamma), acc.x));
545
+ o[1] = __float2half(copysignf(powf(fabsf(acc.y), gamma), acc.y));
546
+ o[2] = __float2half(copysignf(powf(fabsf(acc.z), gamma), acc.z));
547
+ o[3] = __float2half(clamp01(acc.w));
548
+ }
549
+
550
+ // Box-filter the DISPLAY volume (nf remap + normalize included, via
551
+ // sample_i) into a small dense f16 mip: one thread per mip cell, averaging
552
+ // every display voxel that maps into it — one full read of the tensor,
553
+ // once per volume version.
554
+ extern "C" __global__ void bake_mip(
555
+ const unsigned char* __restrict__ data, int dtype,
556
+ int nz, int ny, int nx, int snz, int sny, int snx,
557
+ long long sz, long long sy, long long sx,
558
+ int nf_chop, int nf_along, int nf_chunk,
559
+ float nlo, float nhi, int norm_mode,
560
+ float threshold, float density, float brightness, float contrast, int centered,
561
+ __half* __restrict__ mip, int mz, int my, int mx)
562
+ {
563
+ long long cell = (long long)blockIdx.x * blockDim.x + threadIdx.x;
564
+ long long total = (long long)mz * my * mx;
565
+ if (cell >= total) return;
566
+ Vol v = {data, dtype, nz, ny, nx, snz, sny, snx, sz, sy, sx,
567
+ nf_chop, nf_along, nf_chunk, nlo, nhi, norm_mode};
568
+ int ix = (int)(cell % mx), iy = (int)((cell / mx) % my), iz = (int)(cell / ((long long)mx * my));
569
+ int x0 = (int)((long long)ix * nx / mx), x1 = (int)((long long)(ix + 1) * nx / mx);
570
+ int y0 = (int)((long long)iy * ny / my), y1 = (int)((long long)(iy + 1) * ny / my);
571
+ int z0 = (int)((long long)iz * nz / mz), z1 = (int)((long long)(iz + 1) * nz / mz);
572
+ x1 = max(x1, x0 + 1); y1 = max(y1, y0 + 1); z1 = max(z1, z0 + 1);
573
+ float gate = 1.0f - fminf(fmaxf(threshold, 0.0f), 0.999f);
574
+ float f_acc = 0.0f, k_acc = 0.0f;
575
+ for (int z = z0; z < z1; z++)
576
+ for (int y = y0; y < y1; y++)
577
+ for (int x = x0; x < x1; x++) {
578
+ float m = remapValue(sample_i(v, z, y, x), brightness, contrast,
579
+ centered != 0).y;
580
+ if (m >= gate) { f_acc += 1.0f; }
581
+ else { float r = m / gate; r *= r; r *= r;
582
+ k_acc += r * density * 50.0f; }
583
+ }
584
+ float n = (float)((z1 - z0) * (y1 - y0) * (x1 - x0));
585
+ mip[cell * 2] = __float2half(f_acc / n);
586
+ mip[cell * 2 + 1] = __float2half(k_acc / n);
587
+ }
588
+
589
+ // Bake the floor-shadow map: one thread per texel of a (fh, fw) grid over
590
+ // the floor square [-R, R]^2 at z = -vs.z * plane_side; each runs the SAME
591
+ // transmittance march the live path would — but at FULL RESOLUTION (no
592
+ // mip: software-trilinear source taps, the GL reference's field) and with
593
+ // a deeper step count than the per-frame budget ever allowed. Stores
594
+ // (vis, t_occ) as f16 pairs. Re-run only when the volume / light /
595
+ // transfer changes — never per frame.
596
+ extern "C" __global__ void bake_floor(
597
+ const unsigned char* __restrict__ data, int dtype,
598
+ int nz, int ny, int nx, int snz, int sny, int snx,
599
+ long long sz, long long sy, long long sx,
600
+ int nf_chop, int nf_along, int nf_chunk,
601
+ float nlo, float nhi, int norm_mode,
602
+ float vsx, float vsy, float vsz,
603
+ float threshold, float density, float brightness, float contrast, int centered,
604
+ float lx, float ly, float lz, float plane_side,
605
+ __half* __restrict__ fmap, int fw, int fh, float Rx, float Ry, int steps)
606
+ {
607
+ int ix = blockIdx.x * blockDim.x + threadIdx.x;
608
+ int iy = blockIdx.y * blockDim.y + threadIdx.y;
609
+ if (ix >= fw || iy >= fh) return;
610
+ Ctx c;
611
+ c.v = Vol{data, dtype, nz, ny, nx, snz, sny, snx, sz, sy, sx,
612
+ nf_chop, nf_along, nf_chunk, nlo, nhi, norm_mode};
613
+ c.vs = f3(vsx, vsy, vsz);
614
+ c.gate = 1.0f - fminf(fmaxf(threshold, 0.0f), 0.999f);
615
+ c.density = density; c.brightness = brightness; c.contrast = contrast;
616
+ c.centered = centered != 0;
617
+ c.mip = 0; c.mz = c.my = c.mx = 0; // full-res taps
618
+ float x = ((float)ix + 0.5f) / (float)fw * 2.0f * Rx - Rx;
619
+ float y = ((float)iy + 0.5f) / (float)fh * 2.0f * Ry - Ry;
620
+ float3 pw = f3(x, y, -vsz * plane_side);
621
+ float3 pl = f3(lx, ly, lz * plane_side);
622
+ float2 vi = lightVisibilityInfo(c, pw, pl, steps, 1e8f);
623
+ fmap[((long long)iy * fw + ix) * 2] = __float2half(vi.x);
624
+ fmap[((long long)iy * fw + ix) * 2 + 1] = __float2half(vi.y);
625
+ }
626
+ """
627
+
628
+
629
+ def _kernel_for(dev_index, name="march"):
630
+ return kernel_functions("voxels", dev_index, KERNEL,
631
+ ("march", "bake_mip", "bake_floor"))[name]
632
+
633
+
634
+ SHADE_N = 18
635
+
636
+
637
+ def shade_params(draw_plane=False, shadow_opacity=1.0, shadow_softness=0.15,
638
+ shadow_tint=(0.0, 0.02, 0.05), plane_side=1.0, draw_shading=False,
639
+ self_shading=False, light_pos=(50.0, -50.0, 200.0),
640
+ light_tint=(1.0, 1.0, 1.0), light_brightness=1.622,
641
+ ambient_light=0.3, shading_strength=0.7):
642
+ """The kernel's shading parameters as one float list (the `sh` array;
643
+ order = the Shade struct unpack in KERNEL). Defaults = shading OFF, so a
644
+ bare march() renders the plain colour march."""
645
+ return [1.0 if draw_plane else 0.0, float(shadow_opacity), float(shadow_softness),
646
+ float(shadow_tint[0]), float(shadow_tint[1]), float(shadow_tint[2]),
647
+ float(plane_side), 1.0 if draw_shading else 0.0, 1.0 if self_shading else 0.0,
648
+ float(light_pos[0]), float(light_pos[1]), float(light_pos[2]),
649
+ float(light_tint[0]), float(light_tint[1]), float(light_tint[2]),
650
+ float(light_brightness), float(ambient_light), float(shading_strength)]
651
+
652
+
653
+ def march(view, out, lut, *, display_shape, nf=(-1, -1, 0), norm=(0.0, 1.0, 0),
654
+ tilt=0.0, spin=0.0, roll=0.0, zoom=3.4, pan=(0.0, 0.0, 0.0), ortho=False,
655
+ aspect=1.0, volume_scale=(1.0, 1.0, 1.0), step_size=0.005,
656
+ max_steps=512, density=0.7, threshold=0.3, brightness=1.0,
657
+ contrast=1.0, gamma=1.6, centered=False, shade=None, mip=None,
658
+ floor_map=None, floor_extent=(0.0, 0.0)):
659
+ """Raymarch `view` (a 3-D torch view (z, y, x) on a CUDA device, ANY
660
+ strides, ANY supported dtype) into `out` (torch float16 (H, W, 4) on the
661
+ same device; premultiplied LINEAR RGBA, the GL pass's own output). `lut`
662
+ is a float32 (n, 3) torch tensor on that device — extended sRGB, so an
663
+ HDR table's entries past [0, 1] ride through. `display_shape` = (nz, ny, nx) after neural
664
+ flow; `nf` = (chop_axis, along_axis, chunk) with axes 0=z 1=y 2=x (chop
665
+ -1 = off); `norm` = (lo, hi, mode); `shade` = a float32 torch tensor of
666
+ SHADE_N on the same device (shade_params(...)) or None = shading off.
667
+ `mip` = the shading mip from build_mip() (all shading reads sample it —
668
+ pass it whenever shade enables the plane or shading; without it the
669
+ shading taps fall back to the strided source, correct but ~100× the
670
+ loads on big tensors)."""
671
+ import torch
672
+ assert view.dim() == 3 and out.dim() == 3 and out.shape[2] == 4
673
+ if out.dtype != torch.float16:
674
+ raise ValueError("cuda_march: out must be a float16 (H, W, 4) tensor")
675
+ dev = view.device.index or 0
676
+ if (out.device.index or 0) != dev or (lut.device.index or 0) != dev:
677
+ raise ValueError("cuda_march: view/out/lut must share a device")
678
+ if shade is None:
679
+ shade = torch.tensor(shade_params(), dtype=torch.float32, device=view.device)
680
+ elif (shade.device.index or 0) != dev or shade.numel() < SHADE_N:
681
+ raise ValueError("cuda_march: shade must be a float32[SHADE_N] tensor on the view's device")
682
+ H, W = int(out.shape[0]), int(out.shape[1])
683
+ snz, sny, snx = (int(s) for s in view.shape)
684
+ sz, sy, sx = (int(s) for s in view.stride())
685
+ nz, ny, nx = (int(s) for s in display_shape)
686
+ chop, along, chunk = nf
687
+ f32, i32, i64 = np.float32, np.int32, np.int64
688
+ vsx, vsy, vsz = volume_scale
689
+ with using_device(dev):
690
+ fn = _kernel_for(dev)
691
+ block = (16, 16, 1)
692
+ grid = ((W + 15) // 16, (H + 15) // 16, 1)
693
+ fn(np.uintp(view.data_ptr()), i32(dtype_code(view)),
694
+ i32(nz), i32(ny), i32(nx), i32(snz), i32(sny), i32(snx),
695
+ i64(sz), i64(sy), i64(sx),
696
+ i32(chop), i32(along), i32(chunk),
697
+ f32(norm[0]), f32(norm[1]), i32(norm[2]),
698
+ np.uintp(lut.data_ptr()), i32(lut.shape[0]),
699
+ np.uintp(out.data_ptr()), i32(W), i32(H),
700
+ f32(tilt), f32(spin), f32(roll), f32(zoom), f32(pan[0]), f32(pan[1]), f32(pan[2]),
701
+ i32(1 if ortho else 0), f32(aspect), f32(vsx), f32(vsy), f32(vsz),
702
+ f32(step_size), i32(max_steps), f32(density), f32(threshold),
703
+ f32(brightness), f32(contrast), f32(gamma), i32(1 if centered else 0),
704
+ np.uintp(shade.data_ptr()),
705
+ np.uintp(mip.data_ptr() if mip is not None else 0),
706
+ i32(mip.shape[0] if mip is not None else 0),
707
+ i32(mip.shape[1] if mip is not None else 0),
708
+ i32(mip.shape[2] if mip is not None else 0),
709
+ np.uintp(floor_map.data_ptr() if floor_map is not None else 0),
710
+ i32(floor_map.shape[1] if floor_map is not None else 0),
711
+ i32(floor_map.shape[0] if floor_map is not None else 0),
712
+ f32(floor_extent[0]), f32(floor_extent[1]),
713
+ block=block, grid=grid)
714
+ return out
715
+
716
+
717
+ FLOOR_MARGIN = 1.5 # map half-extent = margin around the box footprint
718
+ FLOOR_TEXELS_PER_VOXEL = 2.0
719
+ FLOOR_MIN_SIZE = 256
720
+ FLOOR_MAX_SIZE = 3072
721
+
722
+
723
+ def floor_map_extent(volume_scale, margin=FLOOR_MARGIN):
724
+ """The baked floor map's half-extents (Rx, Ry): the box footprint with
725
+ margin for the light's slant and the penumbra taps. Deliberately NOT the
726
+ catcher's whole 3x fade region — the default light is near-vertical, so
727
+ the shadow barely leaves the footprint, and hugging it is what buys
728
+ texel density. (A user dragging light_pos to a glancing angle can clip
729
+ at the map edge; raise FLOOR_MARGIN if that ever matters.)"""
730
+ return (margin * float(volume_scale[0]), margin * float(volume_scale[1]))
731
+
732
+
733
+ def floor_map_size(display_shape, margin=FLOOR_MARGIN):
734
+ """(fw, fh) matched to the DATA: ~FLOOR_TEXELS_PER_VOXEL map texels per
735
+ voxel of the floor-facing axes (x = width, y = height), margin included,
736
+ clamped to [FLOOR_MIN_SIZE, FLOOR_MAX_SIZE]. Small footprints get
737
+ oversampled (crisp), huge ones ride the cap (>= ~1 texel/voxel up to
738
+ ~2048-wide axes)."""
739
+ nz, ny, nx = (int(x) for x in display_shape)
740
+ def _sz(n):
741
+ want = int(round(n * margin * FLOOR_TEXELS_PER_VOXEL))
742
+ return max(FLOOR_MIN_SIZE, min(FLOOR_MAX_SIZE, (want + 15) // 16 * 16))
743
+ return _sz(nx), _sz(ny)
744
+
745
+
746
+ def build_floor_map(view, *, display_shape, volume_scale, nf=(-1, -1, 0),
747
+ norm=(0.0, 1.0, 0), threshold=0.3, density=0.7,
748
+ brightness=1.0, contrast=1.0, centered=False,
749
+ light_pos=(50.0, -50.0, 200.0), plane_side=1.0,
750
+ size=None, steps=64):
751
+ """Bake the floor-shadow map: (fh, fw, 2) f16 of (vis, t_occ) over the
752
+ floor rect [-Rx, Rx] x [-Ry, Ry] (floor_map_extent), each texel a
753
+ FULL-RES transmittance march toward the light — precise where the mip
754
+ path smeared, and paid once per (volume, light, transfer) version
755
+ instead of per frame. Resolution matches the data (floor_map_size)
756
+ unless `size` (an (fw, fh) tuple or one int for both) overrides it.
757
+ The extents ride back on the tensor as `fmap.extent`. Same device as
758
+ `view`."""
759
+ import torch
760
+ nz, ny, nx = (int(x) for x in display_shape)
761
+ dev = view.device.index or 0
762
+ if size is None:
763
+ fw, fh = floor_map_size(display_shape)
764
+ elif isinstance(size, int):
765
+ fw = fh = size
766
+ else:
767
+ fw, fh = (int(v) for v in size)
768
+ fmap = torch.empty(fh, fw, 2, dtype=torch.float16, device=view.device)
769
+ snz, sny, snx = (int(x) for x in view.shape)
770
+ sz, sy, sx = (int(x) for x in view.stride())
771
+ chop, along, chunk = nf
772
+ Rx, Ry = floor_map_extent(volume_scale)
773
+ f32, i32, i64 = np.float32, np.int32, np.int64
774
+ with using_device(dev):
775
+ fn = _kernel_for(dev, "bake_floor")
776
+ fn(np.uintp(view.data_ptr()), i32(dtype_code(view)),
777
+ i32(nz), i32(ny), i32(nx), i32(snz), i32(sny), i32(snx),
778
+ i64(sz), i64(sy), i64(sx),
779
+ i32(chop), i32(along), i32(chunk),
780
+ f32(norm[0]), f32(norm[1]), i32(norm[2]),
781
+ f32(volume_scale[0]), f32(volume_scale[1]), f32(volume_scale[2]),
782
+ f32(threshold), f32(density), f32(brightness), f32(contrast),
783
+ i32(1 if centered else 0),
784
+ f32(light_pos[0]), f32(light_pos[1]), f32(light_pos[2]), f32(plane_side),
785
+ np.uintp(fmap.data_ptr()), i32(fw), i32(fh), f32(Rx), f32(Ry), i32(steps),
786
+ block=(16, 16, 1), grid=((fw + 15) // 16, (fh + 15) // 16, 1))
787
+ fmap.extent = (Rx, Ry)
788
+ return fmap
789
+
790
+
791
+ def build_mip(view, *, display_shape, nf=(-1, -1, 0), norm=(0.0, 1.0, 0),
792
+ threshold=0.3, density=0.7, brightness=1.0, contrast=1.0,
793
+ centered=False, cap=128):
794
+ """Bake the shading OPACITY mip: a dense f16 (mz, my, mx) grid, each
795
+ cell (f = hard-occluder fraction, k = mean sub-gate opacity density)
796
+ of its block under the given transfer (nf remap + normalize applied) — the
797
+ march reconstructs a step over L physical voxels as
798
+ T = ((1-f)(1-k*s_vox))^L, so a coarse cell transmits the same light as
799
+ the voxels it replaces (value-space bakes made blocky cells shade
800
+ darker than the data). Bakes THROUGH the transfer, so cache it keyed
801
+ on those params too. One full tensor read; a few MB, L2-resident.
802
+ Same device as `view`."""
803
+ import torch
804
+ nz, ny, nx = (int(x) for x in display_shape)
805
+ mz, my, mx = min(cap, nz), min(cap, ny), min(cap, nx)
806
+ dev = view.device.index or 0
807
+ mip = torch.empty(mz, my, mx, 2, dtype=torch.float16, device=view.device)
808
+ snz, sny, snx = (int(x) for x in view.shape)
809
+ sz, sy, sx = (int(x) for x in view.stride())
810
+ chop, along, chunk = nf
811
+ f32, i32, i64 = np.float32, np.int32, np.int64
812
+ total = mz * my * mx
813
+ with using_device(dev):
814
+ fn = _kernel_for(dev, "bake_mip")
815
+ fn(np.uintp(view.data_ptr()), i32(dtype_code(view)),
816
+ i32(nz), i32(ny), i32(nx), i32(snz), i32(sny), i32(snx),
817
+ i64(sz), i64(sy), i64(sx),
818
+ i32(chop), i32(along), i32(chunk),
819
+ f32(norm[0]), f32(norm[1]), i32(norm[2]),
820
+ f32(threshold), f32(density), f32(brightness), f32(contrast),
821
+ i32(1 if centered else 0),
822
+ np.uintp(mip.data_ptr()), i32(mz), i32(my), i32(mx),
823
+ block=(256, 1, 1), grid=((total + 255) // 256, 1, 1))
824
+ return mip