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.
- meltygui/__init__.py +107 -0
- meltygui/accounts/__init__.py +0 -0
- meltygui/accounts/internet_accounts.py +1355 -0
- meltygui/chat/__init__.py +91 -0
- meltygui/chat/activity.py +75 -0
- meltygui/chat/backends.py +36 -0
- meltygui/chat/chat_interface.py +732 -0
- meltygui/chat/chat_proxy.py +352 -0
- meltygui/chat/codex_proxy.py +592 -0
- meltygui/chat/codex_settings.py +100 -0
- meltygui/chat/codex_transport.py +60 -0
- meltygui/chat/command_parser.py +204 -0
- meltygui/chat/images.py +227 -0
- meltygui/chat/messages.py +417 -0
- meltygui/chat/metadata.py +139 -0
- meltygui/chat/writer_locks.py +64 -0
- meltygui/code/__init__.py +0 -0
- meltygui/code/basic_converters.py +533 -0
- meltygui/code/chain_converters.py +2111 -0
- meltygui/code/code_checks.py +2209 -0
- meltygui/code/core_syntax.py +1430 -0
- meltygui/code/file_converters.py +1933 -0
- meltygui/code/fileref.py +702 -0
- meltygui/code/hotswap_guard.py +144 -0
- meltygui/code/libcst_conversion.py +9724 -0
- meltygui/code/live_instrument.py +392 -0
- meltygui/code/live_view.py +2490 -0
- meltygui/code/melty_scan.py +2684 -0
- meltygui/code/new_codecs.py +1255 -0
- meltygui/code/new_converters.py +3017 -0
- meltygui/code/project_code.py +278 -0
- meltygui/code/source_context.py +63 -0
- meltygui/code/symbol_roster.py +1588 -0
- meltygui/code/syntax_check.py +34 -0
- meltygui/code/syntax_check_worker.py +114 -0
- meltygui/completion/__init__.py +0 -0
- meltygui/completion/fim.py +1232 -0
- meltygui/completion/fim_context.py +481 -0
- meltygui/completion/providers/__init__.py +0 -0
- meltygui/completion/providers/anthropic_oauth.py +446 -0
- meltygui/completion/providers/anthropic_requests.py +69 -0
- meltygui/completion/providers/claude.py +203 -0
- meltygui/completion/providers/claude_usage.py +499 -0
- meltygui/completion/providers/codex_accounts.py +180 -0
- meltygui/completion/providers/copilot.py +617 -0
- meltygui/completion/providers/oauth_popup.py +220 -0
- meltygui/completion/providers/ollama.py +320 -0
- meltygui/completion/providers/profiles.py +23 -0
- meltygui/core/README.md +88 -0
- meltygui/core/__init__.py +1 -0
- meltygui/core/automation/__init__.py +1 -0
- meltygui/core/automation/action_core.py +153 -0
- meltygui/core/automation/collection_action.py +45 -0
- meltygui/core/automation/mcp_eval.py +167 -0
- meltygui/core/automation/mcp_hotswap.py +107 -0
- meltygui/core/automation/mcp_query.py +552 -0
- meltygui/core/automation/mcp_server.py +657 -0
- meltygui/core/automation/orchestration_core.py +2636 -0
- meltygui/core/automation/query_core.py +124 -0
- meltygui/core/automation/search_core.py +26 -0
- meltygui/core/automation/selector_core.py +340 -0
- meltygui/core/automation/value_core.py +1752 -0
- meltygui/core/cache/__init__.py +1 -0
- meltygui/core/cache/cache_diagnostics.py +0 -0
- meltygui/core/cache/invalidation_decoration.py +153 -0
- meltygui/core/cache/invalidation_tracker.py +43 -0
- meltygui/core/cache/tile_cache.py +5968 -0
- meltygui/core/conversion/__init__.py +1 -0
- meltygui/core/conversion/bubbling.py +599 -0
- meltygui/core/conversion/cache_tree.py +188 -0
- meltygui/core/conversion/chain.py +113 -0
- meltygui/core/conversion/converter_register.py +145 -0
- meltygui/core/conversion/data_decoration.py +67 -0
- meltygui/core/conversion/dict_conversion.py +1815 -0
- meltygui/core/conversion/dict_conversion_util.py +177 -0
- meltygui/core/conversion/dynamic_obj.py +89 -0
- meltygui/core/conversion/graph_compare.py +183 -0
- meltygui/core/conversion/load_save_v2.py +1032 -0
- meltygui/core/conversion/missing_saved_class.py +39 -0
- meltygui/core/conversion/path_finder.py +606 -0
- meltygui/core/conversion/render_host.py +1083 -0
- meltygui/core/core_render.py +6537 -0
- meltygui/core/definition_hotswap.py +231 -0
- meltygui/core/diagnostics/__init__.py +1 -0
- meltygui/core/diagnostics/attribute_churn.py +38 -0
- meltygui/core/diagnostics/fps_counter.py +39 -0
- meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
- meltygui/core/diagnostics/inspection_core.py +169 -0
- meltygui/core/diagnostics/monitor_core.py +105 -0
- meltygui/core/diagnostics/notifications.py +706 -0
- meltygui/core/diagnostics/perf_trace.py +281 -0
- meltygui/core/diagnostics/profile_decoration.py +88 -0
- meltygui/core/diagnostics/resize_trace.py +62 -0
- meltygui/core/diagnostics/screenshot_core.py +247 -0
- meltygui/core/diagnostics/session_status.py +98 -0
- meltygui/core/diagnostics/trace_core.py +445 -0
- meltygui/core/files/__init__.py +1 -0
- meltygui/core/files/file_core.py +208 -0
- meltygui/core/files/file_explorer_core.py +104 -0
- meltygui/core/files/file_tree_core.py +198 -0
- meltygui/core/files/file_watch_core.py +43 -0
- meltygui/core/files/import_graph_core.py +43 -0
- meltygui/core/files/metadata_core.py +51 -0
- meltygui/core/graphics/__init__.py +1 -0
- meltygui/core/graphics/cuda_context_core.py +166 -0
- meltygui/core/graphics/cuda_interop_core.py +136 -0
- meltygui/core/graphics/cuda_kernel_core.py +91 -0
- meltygui/core/graphics/framebuffer_recorder.py +337 -0
- meltygui/core/graphics/gl_state.py +658 -0
- meltygui/core/graphics/lut_core.py +52 -0
- meltygui/core/graphics/overlay_renderer.py +984 -0
- meltygui/core/graphics/scene_target.py +180 -0
- meltygui/core/graphics/screenshot.py +439 -0
- meltygui/core/graphics/shader_func.py +478 -0
- meltygui/core/graphics/tensor_core.py +45 -0
- meltygui/core/graphics/text_texture.py +329 -0
- meltygui/core/graphics/wayland_color.py +635 -0
- meltygui/core/input/__init__.py +1 -0
- meltygui/core/input/collision.py +165 -0
- meltygui/core/input/drag_drop_core.py +1525 -0
- meltygui/core/input/hypr_left_drag.py +323 -0
- meltygui/core/input/input_core.py +245 -0
- meltygui/core/input/input_handler.py +1101 -0
- meltygui/core/input/mouse_cursor.py +355 -0
- meltygui/core/input/pynput_backend.py +1054 -0
- meltygui/core/input/space_mouse.py +338 -0
- meltygui/core/input/touchpad_backend.py +393 -0
- meltygui/core/input/view_selection.py +177 -0
- meltygui/core/layout/__init__.py +1 -0
- meltygui/core/layout/column_core.py +2153 -0
- meltygui/core/layout/cursor_core.py +161 -0
- meltygui/core/layout/dropdown_core.py +278 -0
- meltygui/core/layout/edge_constraints.py +155 -0
- meltygui/core/layout/grid_core.py +117 -0
- meltygui/core/layout/header_core.py +23 -0
- meltygui/core/layout/header_runtime.py +67 -0
- meltygui/core/layout/layout_core.py +87 -0
- meltygui/core/layout/tile_manager_core.py +591 -0
- meltygui/core/melty.py +6726 -0
- meltygui/core/module_map.json +896 -0
- meltygui/core/module_names.py +19 -0
- meltygui/core/rendering/__init__.py +1 -0
- meltygui/core/rendering/core_decoration.py +431 -0
- meltygui/core/rendering/core_render_helpers.py +328 -0
- meltygui/core/rendering/func_metadata.py +398 -0
- meltygui/core/rendering/mode.py +818 -0
- meltygui/core/rendering/mode_defaults.py +41 -0
- meltygui/core/rendering/modes.py +136 -0
- meltygui/core/rendering/parameter_core.py +1665 -0
- meltygui/core/rendering/render_dispatch.py +1891 -0
- meltygui/core/rendering/render_funcs.py +273 -0
- meltygui/core/rendering/shaped.py +312 -0
- meltygui/core/rendering/window_decoration.py +25 -0
- meltygui/core/runtime/__init__.py +1 -0
- meltygui/core/runtime/app.py +735 -0
- meltygui/core/runtime/app_session.py +140 -0
- meltygui/core/runtime/background.py +564 -0
- meltygui/core/runtime/extensions.py +53 -0
- meltygui/core/runtime/gc_manager.py +1163 -0
- meltygui/core/runtime/lifecycle.py +21 -0
- meltygui/core/runtime/paths.py +27 -0
- meltygui/core/runtime/settings.py +14 -0
- meltygui/core/runtime/singleton.py +16 -0
- meltygui/core/runtime/thread_safe_bool.py +24 -0
- meltygui/core/runtime/thread_signal.py +30 -0
- meltygui/core/runtime/toggles.py +3115 -0
- meltygui/core/services/__init__.py +1 -0
- meltygui/core/services/account_core.py +10 -0
- meltygui/core/services/chat_core.py +19 -0
- meltygui/core/services/claude_terminal_core.py +346 -0
- meltygui/core/services/terminal_core.py +458 -0
- meltygui/core/services/terminal_runtime.py +78 -0
- meltygui/core/styling/__init__.py +1 -0
- meltygui/core/styling/color_core.py +46 -0
- meltygui/core/styling/fonts.py +639 -0
- meltygui/core/styling/global_style.py +338 -0
- meltygui/core/styling/style.py +198 -0
- meltygui/core/styling/style_core.py +522 -0
- meltygui/core/styling/warm_start.py +149 -0
- meltygui/core/windowing/__init__.py +1 -0
- meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
- meltygui/core/windowing/backends/__init__.py +1 -0
- meltygui/core/windowing/backends/imgui_renderer.py +138 -0
- meltygui/core/windowing/backends/native_wayland.py +850 -0
- meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
- meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
- meltygui/core/windowing/backends/wayland_protocol.py +101 -0
- meltygui/core/windowing/dock_core.py +182 -0
- meltygui/core/windowing/frame_geometry.py +42 -0
- meltygui/core/windowing/geometry_feed.py +864 -0
- meltygui/core/windowing/glfw_utils.py +1343 -0
- meltygui/core/windowing/os_frame.py +1552 -0
- meltygui/core/windowing/surface.py +643 -0
- meltygui/core/windowing/titlebar.py +1560 -0
- meltygui/core/windowing/titlebar_buttons.py +281 -0
- meltygui/core/windowing/wayland_move.py +932 -0
- meltygui/core/windowing/window_api.py +62 -0
- meltygui/core/windowing/window_constants.py +339 -0
- meltygui/core/windowing/window_visibility.py +162 -0
- meltygui/debug/__init__.py +0 -0
- meltygui/debug/app_view_utils.py +9 -0
- meltygui/editor/__init__.py +0 -0
- meltygui/editor/bash_syntax.py +30 -0
- meltygui/editor/code_line_fast.py +152 -0
- meltygui/editor/diff.py +139 -0
- meltygui/editor/external_changes.py +159 -0
- meltygui/editor/file_header.py +41 -0
- meltygui/editor/live_usage.py +169 -0
- meltygui/editor/live_view_views.py +1803 -0
- meltygui/editor/pending_save.py +1351 -0
- meltygui/editor/roster_tints.py +547 -0
- meltygui/editor/source_preview.py +16 -0
- meltygui/editor/source_tools.py +10 -0
- meltygui/editor/source_ui.py +70 -0
- meltygui/editor/spell_check.py +77 -0
- meltygui/editor/text_editor.py +9076 -0
- meltygui/editor/usage_picker.py +538 -0
- meltygui/events/__init__.py +0 -0
- meltygui/events/example.py +118 -0
- meltygui/examples/__init__.py +0 -0
- meltygui/examples/columns_demo.py +33 -0
- meltygui/examples/columns_window_demo.py +60 -0
- meltygui/examples/context_menu_demo.py +24 -0
- meltygui/examples/context_menu_window_demo.py +49 -0
- meltygui/examples/gui_playground.py +127 -0
- meltygui/examples/live_view_playground.py +256 -0
- meltygui/examples/lora.py +43 -0
- meltygui/examples/lora_data.py +59 -0
- meltygui/examples/lora_policies.py +67 -0
- meltygui/examples/mode_demo.py +49 -0
- meltygui/examples/modifies_demo.py +119 -0
- meltygui/examples/scalar_policies.py +60 -0
- meltygui/examples/style_layouts.py +149 -0
- meltygui/examples/tile_manager_demo.py +74 -0
- meltygui/examples/tint_demo.py +160 -0
- meltygui/examples/tint_functions.py +56 -0
- meltygui/examples/trace_demo.py +88 -0
- meltygui/examples/two_windows.py +36 -0
- meltygui/files/__init__.py +0 -0
- meltygui/files/fast_file_explorer.py +439 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
- meltygui/graphics/__init__.py +6 -0
- meltygui/graphics/base.py +85 -0
- meltygui/graphics/examples.py +507 -0
- meltygui/graphics/executor.py +520 -0
- meltygui/graphics/filter.py +667 -0
- meltygui/graphics/filter.pyi +856 -0
- meltygui/graphics/generate_stubs.py +22 -0
- meltygui/graphics/registry.py +203 -0
- meltygui/graphics/shader_compiler.py +155 -0
- meltygui/graphics/shaders.py +1302 -0
- meltygui/graphics/stub_generator.py +250 -0
- meltygui/graphics/texture_manager.py +170 -0
- meltygui/graphics/texture_min_max.py +295 -0
- meltygui/hdr_color.py +757 -0
- meltygui/image_load.py +308 -0
- meltygui/model/__init__.py +1 -0
- meltygui/model/account_model.py +142 -0
- meltygui/model/camera_model.py +159 -0
- meltygui/model/chat_model.py +86 -0
- meltygui/model/code_model.py +62 -0
- meltygui/model/code_proxy_model.py +876 -0
- meltygui/model/collection_model.py +41 -0
- meltygui/model/color_model.py +127 -0
- meltygui/model/cuda_tensor_model.py +36 -0
- meltygui/model/cuda_texture_model.py +149 -0
- meltygui/model/dropdown_model.py +137 -0
- meltygui/model/file_metadata_model.py +73 -0
- meltygui/model/file_model.py +289 -0
- meltygui/model/format_model.py +390 -0
- meltygui/model/graph_model.py +98 -0
- meltygui/model/icon_model.py +1024 -0
- meltygui/model/import_graph_model.py +468 -0
- meltygui/model/layout_model.py +15 -0
- meltygui/model/lut_model.py +266 -0
- meltygui/model/search_model.py +173 -0
- meltygui/model/tensor_model.py +402 -0
- meltygui/model/terminal_model.py +329 -0
- meltygui/model/texture_model.py +146 -0
- meltygui/model/tile_model.py +24 -0
- meltygui/model/trace_model.py +198 -0
- meltygui/model/trace_report_model.py +146 -0
- meltygui/models/__init__.py +0 -0
- meltygui/models/file_meta.py +613 -0
- meltygui/models/function_console.py +184 -0
- meltygui/models/orchestration.py +48 -0
- meltygui/pbr.py +1576 -0
- meltygui/png_unfilter.c +49 -0
- meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
- meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
- meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
- meltygui/resources/dejavu/LICENSE.txt +78 -0
- meltygui/resources/fontawesome-LICENSE.txt +121 -0
- meltygui/resources/fontawesome-webfont.ttf +0 -0
- meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
- meltygui/resources/jetbrains-weights/OFL.txt +93 -0
- meltygui/resources/jetbrains-weights/README.md +2 -0
- meltygui/state/__init__.py +0 -0
- meltygui/state/account_state.py +22 -0
- meltygui/state/animation_state.py +97 -0
- meltygui/state/annotation_state.py +22 -0
- meltygui/state/chat_state.py +36 -0
- meltygui/state/code_state.py +10 -0
- meltygui/state/core_enums.py +59 -0
- meltygui/state/core_markers.py +115 -0
- meltygui/state/core_undo.py +1075 -0
- meltygui/state/file_state.py +75 -0
- meltygui/state/graph_state.py +26 -0
- meltygui/state/inspection_state.py +47 -0
- meltygui/state/menu_state.py +10 -0
- meltygui/state/model_enums.py +11 -0
- meltygui/state/new_core_model.py +2394 -0
- meltygui/state/orchestration_state.py +14 -0
- meltygui/state/query_state.py +24 -0
- meltygui/state/tensor_state.py +10 -0
- meltygui/state/terminal_state.py +23 -0
- meltygui/state/trace_state.py +32 -0
- meltygui/state/voxel_state.py +12 -0
- meltygui/text_index.py +816 -0
- meltygui/utils/__init__.py +0 -0
- meltygui/utils/jump_to_code.py +344 -0
- meltygui/utils/pkl_inspect.py +90 -0
- meltygui/utils/render_utils.py +1419 -0
- meltygui/view/__init__.py +1 -0
- meltygui/view/account_view.py +524 -0
- meltygui/view/action_view.py +73 -0
- meltygui/view/chat_decoration_view.py +112 -0
- meltygui/view/chat_view.py +1703 -0
- meltygui/view/code_view.py +2677 -0
- meltygui/view/collection_view.py +1185 -0
- meltygui/view/color_view.py +824 -0
- meltygui/view/control_view.py +559 -0
- meltygui/view/decoration_view.py +439 -0
- meltygui/view/diagnostic_view.py +177 -0
- meltygui/view/dropdown_view.py +1082 -0
- meltygui/view/file_view.py +1599 -0
- meltygui/view/graph_cuda_view.py +105 -0
- meltygui/view/graph_view.py +882 -0
- meltygui/view/header_view.py +848 -0
- meltygui/view/input_view.py +238 -0
- meltygui/view/inspection_view.py +1783 -0
- meltygui/view/layout_view.py +596 -0
- meltygui/view/lut_view.py +45 -0
- meltygui/view/menu_view.py +212 -0
- meltygui/view/orchestration_view.py +658 -0
- meltygui/view/query_view.py +118 -0
- meltygui/view/search_view.py +342 -0
- meltygui/view/tab_view.py +258 -0
- meltygui/view/tensor_view.py +430 -0
- meltygui/view/terminal_view.py +355 -0
- meltygui/view/text_view.py +8031 -0
- meltygui/view/texture_view.py +480 -0
- meltygui/view/tile_view.py +38 -0
- meltygui/view/trace_view.py +923 -0
- meltygui/view/voxel_cuda_view.py +824 -0
- meltygui/view/voxel_view.py +1860 -0
- meltygui/view/window_view.py +111 -0
- meltygui-0.1.0.dist-info/METADATA +143 -0
- meltygui-0.1.0.dist-info/RECORD +372 -0
- meltygui-0.1.0.dist-info/WHEEL +4 -0
- 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
|