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,1302 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Built-in shader types and common shaders.
|
|
3
|
+
|
|
4
|
+
These are automatically registered when importing meltygui.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from meltygui.graphics.registry import register_shader_type
|
|
8
|
+
from meltygui.graphics.registry import register_shader
|
|
9
|
+
from meltygui.graphics.base import GLType
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@register_shader
|
|
13
|
+
class ShadowCast:
|
|
14
|
+
"""
|
|
15
|
+
Shadow cast using light transmission model.
|
|
16
|
+
Multiple shadows combine correctly via multiplication.
|
|
17
|
+
"""
|
|
18
|
+
shader_type = 'standard'
|
|
19
|
+
offset_factor = 0.04
|
|
20
|
+
uniforms = {
|
|
21
|
+
'light_dir': (GLType.VEC2, (-0.5 * offset_factor, 1.6 * offset_factor)),
|
|
22
|
+
'height_scale': (GLType.FLOAT, 0.5),
|
|
23
|
+
'blur_scale': (GLType.FLOAT, 0.3),
|
|
24
|
+
# CONTACT-HARDENING curve: how fast the penumbra grows with the
|
|
25
|
+
# fragment's distance from the caster's silhouette edge (measured
|
|
26
|
+
# in-shader by bisecting along light_dir, normalized 0 at contact,
|
|
27
|
+
# 1 at the shadow tip). blur_scale stays the magnitude at the far
|
|
28
|
+
# end; this shapes the ramp: < 1 blooms the blur rapidly just past
|
|
29
|
+
# the contact edge, 1 = linear growth, > 1 stays crisp for most of
|
|
30
|
+
# the shadow and only softens the tip. 0 = the legacy behavior
|
|
31
|
+
# (uniform blur across the whole shadow).
|
|
32
|
+
'blur_exponent': (GLType.FLOAT, 1.0),
|
|
33
|
+
'max_steps': (GLType.INT, 64),
|
|
34
|
+
'blur_samples': (GLType.INT, 8),
|
|
35
|
+
# Occlusion each caster hit contributes before the depth gap decay:
|
|
36
|
+
# the base darkness of a shadow right at its caster (per-sample,
|
|
37
|
+
# accumulated into coverage).
|
|
38
|
+
'hit_strength': (GLType.FLOAT, 0.7),
|
|
39
|
+
# How fast that contribution decays per unit of caster/receiver
|
|
40
|
+
# depth gap: higher = deep stacks fade their shadows out sooner
|
|
41
|
+
# (contribution clamps at 0, never lightens).
|
|
42
|
+
'hit_falloff': (GLType.FLOAT, 14.0),
|
|
43
|
+
'depth_bias': (GLType.FLOAT, 0.00),
|
|
44
|
+
'surface_threshold': (GLType.FLOAT, -10.0),
|
|
45
|
+
'min_height_diff': (GLType.FLOAT, 0.000),
|
|
46
|
+
'shadow_strength': (GLType.FLOAT, 0.4),
|
|
47
|
+
# Scale applied to raw depth-texture reads. Helps the caller bind the
|
|
48
|
+
# UI's F16 rank mask directly instead of a pre-normalized RGBA8 copy:
|
|
49
|
+
# 8-bit quantization there was ~6.6 depth slices per quantum, so
|
|
50
|
+
# caster/receiver gaps rounded differently every time a window's
|
|
51
|
+
# depth slot changed and shadow intensity visibly wandered.
|
|
52
|
+
'depth_scale': (GLType.FLOAT, 1.0),
|
|
53
|
+
'texture_size': (GLType.VEC2, None),
|
|
54
|
+
}
|
|
55
|
+
fragment_code = """
|
|
56
|
+
void main() {
|
|
57
|
+
vec2 uv = v_texcoord;
|
|
58
|
+
float receiver_depth = texture(u_texture, uv).r * depth_scale;
|
|
59
|
+
|
|
60
|
+
if (receiver_depth < surface_threshold) {
|
|
61
|
+
fragColor = vec4(0.0, 0.0, 0.0, 0.0);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
vec2 light_normalized = normalize(light_dir);
|
|
66
|
+
float min_offset = 0.0;
|
|
67
|
+
|
|
68
|
+
// float min_offset = (0.35) * height_scale;
|
|
69
|
+
float depth_step = 1.0 / float(max_steps);
|
|
70
|
+
|
|
71
|
+
// Start with full light, each caster blocks some
|
|
72
|
+
float light = 1.0;
|
|
73
|
+
float max_height_diff = 0.0;
|
|
74
|
+
|
|
75
|
+
for (float i = 0.5; i <= 16.0; i++) {
|
|
76
|
+
float test_caster_depth = receiver_depth + float(i) * depth_step;
|
|
77
|
+
|
|
78
|
+
float height_diff = test_caster_depth - receiver_depth;
|
|
79
|
+
|
|
80
|
+
if (height_diff < min_height_diff) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
float shadow_offset = max(height_diff * height_scale, min_offset);
|
|
85
|
+
vec2 base_sample_pos = uv + light_normalized * shadow_offset;
|
|
86
|
+
|
|
87
|
+
// Contact-hardening: the penumbra should grow with the fragment's
|
|
88
|
+
// DISTANCE FROM THE CASTER EDGE along the shadow, not just the
|
|
89
|
+
// depth gap (which is constant across a flat caster's whole
|
|
90
|
+
// shadow, making the blur uniform from contact edge to tip). The
|
|
91
|
+
// caster lies toward +light_dir from any shadowed fragment, so
|
|
92
|
+
// bisect [0, shadow_offset] for the nearest sample inside the
|
|
93
|
+
// caster: fragments hugging the silhouette find it almost
|
|
94
|
+
// immediately (edge_dist ~ 0, crisp), fragments at the shadow tip
|
|
95
|
+
// only at full offset (edge_dist ~ 1, full blur). Gated on the
|
|
96
|
+
// base sample actually hitting the caster — penumbra-fringe
|
|
97
|
+
// fragments whose center sample misses keep edge_dist = 1 (they
|
|
98
|
+
// ARE the soft tail). Assumes a contiguous caster interval, true
|
|
99
|
+
// for the UI's convex rects/strips.
|
|
100
|
+
float edge_dist = 1.0;
|
|
101
|
+
float d_at_offset = texture(u_texture, base_sample_pos).r * depth_scale;
|
|
102
|
+
if (blur_exponent > 0.0
|
|
103
|
+
&& d_at_offset >= test_caster_depth - depth_bias) {
|
|
104
|
+
float t_lo = 0.0;
|
|
105
|
+
float t_hi = shadow_offset;
|
|
106
|
+
for (int b = 0; b < 5; b++) {
|
|
107
|
+
float mid = 0.5 * (t_lo + t_hi);
|
|
108
|
+
float d_mid = texture(u_texture,
|
|
109
|
+
uv + light_normalized * mid).r
|
|
110
|
+
* depth_scale;
|
|
111
|
+
if (d_mid >= test_caster_depth - depth_bias) {
|
|
112
|
+
t_hi = mid;
|
|
113
|
+
} else {
|
|
114
|
+
t_lo = mid;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
edge_dist = t_hi / max(shadow_offset, 1e-6);
|
|
118
|
+
}
|
|
119
|
+
// blur_exponent = 0 -> pow term is 1 -> legacy uniform blur.
|
|
120
|
+
float blur_radius = height_diff * blur_scale
|
|
121
|
+
* pow(max(edge_dist, 1e-4), blur_exponent);
|
|
122
|
+
|
|
123
|
+
float hits = 0.0;
|
|
124
|
+
float total_samples = 0.0;
|
|
125
|
+
|
|
126
|
+
for (int s = 0; s < blur_samples; s++) {
|
|
127
|
+
float angle = float(s) * 6.28318 / float(blur_samples);
|
|
128
|
+
|
|
129
|
+
for (int r = 0; r <= 2; r++) {
|
|
130
|
+
float radius = blur_radius * float(r) / 2.0;
|
|
131
|
+
vec2 offset = vec2(cos(angle), sin(angle)) * radius;
|
|
132
|
+
vec2 sample_pos = base_sample_pos + offset;
|
|
133
|
+
|
|
134
|
+
if (sample_pos.x < 0.0 || sample_pos.x > 1.0 ||
|
|
135
|
+
sample_pos.y < 0.0 || sample_pos.y > 1.0) {
|
|
136
|
+
total_samples += 1.0;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
float scene_depth = texture(u_texture, sample_pos).r * depth_scale;
|
|
141
|
+
|
|
142
|
+
if (scene_depth >= test_caster_depth - depth_bias) {
|
|
143
|
+
// Clamped at 0 so a large falloff fades a slice's
|
|
144
|
+
// contribution out instead of going negative and
|
|
145
|
+
// eating other slices' coverage.
|
|
146
|
+
hits += max(0.0, hit_strength - height_diff * hit_falloff);
|
|
147
|
+
}
|
|
148
|
+
total_samples += 1.0;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
float coverage = hits / total_samples;
|
|
153
|
+
|
|
154
|
+
if (coverage > 0.0) {
|
|
155
|
+
// Multiply remaining light by (1 - blockage)
|
|
156
|
+
// This correctly combines overlapping soft shadows
|
|
157
|
+
light *= (1.0 - coverage);
|
|
158
|
+
max_height_diff = max(max_height_diff, height_diff);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Shadow is inverse of remaining light
|
|
163
|
+
float shadow_intensity = (1.0 - light) * shadow_strength;
|
|
164
|
+
|
|
165
|
+
fragColor = vec4(shadow_intensity, max_height_diff, 0.0, receiver_depth);
|
|
166
|
+
}
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
@register_shader
|
|
170
|
+
class ShadowBlur:
|
|
171
|
+
"""
|
|
172
|
+
Pass 2: Distance-dependent blur of the shadow map.
|
|
173
|
+
|
|
174
|
+
Shadows that traveled farther get blurred more, creating soft penumbra.
|
|
175
|
+
|
|
176
|
+
Input: Output from ShadowCast
|
|
177
|
+
Output: Blurred shadow map (same format)
|
|
178
|
+
"""
|
|
179
|
+
shader_type = 'standard'
|
|
180
|
+
uniforms = {
|
|
181
|
+
'blur_scale': (GLType.FLOAT, 20.0), # Max blur radius in pixels
|
|
182
|
+
'min_blur': (GLType.FLOAT, 1.0), # Minimum blur (keeps some softness)
|
|
183
|
+
'texture_size': (GLType.VEC2, None),
|
|
184
|
+
}
|
|
185
|
+
fragment_code = """
|
|
186
|
+
void main() {
|
|
187
|
+
vec2 uv = v_texcoord;
|
|
188
|
+
vec2 texel = 1.0 / texture_size;
|
|
189
|
+
|
|
190
|
+
vec4 center = texture(u_texture, uv);
|
|
191
|
+
float travel_dist = center.g; // How far the shadow traveled
|
|
192
|
+
float receiver_depth = center.a;
|
|
193
|
+
|
|
194
|
+
// Blur radius scales with travel distance
|
|
195
|
+
float blur_radius = mix(min_blur, blur_scale, travel_dist);
|
|
196
|
+
int r = int(ceil(blur_radius));
|
|
197
|
+
|
|
198
|
+
if (r <= 0) {
|
|
199
|
+
fragColor = center;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Gaussian blur with variable radius
|
|
204
|
+
vec4 sum = vec4(0.0);
|
|
205
|
+
float total_weight = 0.0;
|
|
206
|
+
float sigma = blur_radius / 3.0;
|
|
207
|
+
float sigma2 = sigma * sigma;
|
|
208
|
+
|
|
209
|
+
for (int x = -r; x <= r; x++) {
|
|
210
|
+
for (int y = -r; y <= r; y++) {
|
|
211
|
+
vec2 offset = vec2(float(x), float(y));
|
|
212
|
+
vec2 sample_uv = uv + offset * texel;
|
|
213
|
+
|
|
214
|
+
vec4 sample_val = texture(u_texture, sample_uv);
|
|
215
|
+
|
|
216
|
+
// Only blur shadows that are on surfaces at similar depth
|
|
217
|
+
// This prevents shadow bleeding across depth discontinuities
|
|
218
|
+
float depth_diff = abs(sample_val.a - receiver_depth);
|
|
219
|
+
float depth_weight = exp(-depth_diff * 50.0);
|
|
220
|
+
|
|
221
|
+
// Gaussian weight
|
|
222
|
+
float dist2 = float(x*x + y*y);
|
|
223
|
+
float gauss_weight = exp(-dist2 / (2.0 * sigma2));
|
|
224
|
+
|
|
225
|
+
float weight = gauss_weight * depth_weight;
|
|
226
|
+
sum += sample_val * weight;
|
|
227
|
+
total_weight += weight;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
fragColor = sum / total_weight;
|
|
232
|
+
}
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@register_shader
|
|
237
|
+
class ShadowComposite:
|
|
238
|
+
"""
|
|
239
|
+
Pass 3: Composite shadows onto the original image.
|
|
240
|
+
|
|
241
|
+
Input uniforms:
|
|
242
|
+
u_texture: Original color texture
|
|
243
|
+
shadow_map: Blurred shadow map from Pass 2
|
|
244
|
+
depth_map: Original depth map
|
|
245
|
+
|
|
246
|
+
Output: Final composited image with shadows
|
|
247
|
+
"""
|
|
248
|
+
shader_type = 'standard'
|
|
249
|
+
uniforms = {
|
|
250
|
+
'shadow_map': (GLType.SAMPLER2D, None),
|
|
251
|
+
'depth_map': (GLType.SAMPLER2D, None),
|
|
252
|
+
'shadow_opacity': (GLType.FLOAT, 1.0), # How dark shadows get
|
|
253
|
+
'shadow_color': (GLType.VEC3, (0.0, 0.0, 0.0)), # Shadow tint
|
|
254
|
+
# Resolution of shadow_map. May be lower than the screen when the shadow
|
|
255
|
+
# map is downscaled for performance; used to drive the bilateral upsample.
|
|
256
|
+
'shadow_size': (GLType.VEC2, None),
|
|
257
|
+
# How aggressively the upsample snaps the shadow to depth edges. Higher =
|
|
258
|
+
# crisper edges (less fringing), lower = softer. Depths are in [0, 1].
|
|
259
|
+
'depth_sharpness': (GLType.FLOAT, 50.0),
|
|
260
|
+
# Same sample depth scale as ShadowCast's depth_scale - depth_map is the
|
|
261
|
+
# raw R16 depth mask, and s.a (receiver depth baked by ShadowCast) is
|
|
262
|
+
# already scaled, so the bilateral compare needs both in the same units.
|
|
263
|
+
'depth_scale': (GLType.FLOAT, 1.0),
|
|
264
|
+
# Glow light buffer (TileCacheMasked add_glow marks, low-res RGBA16F):
|
|
265
|
+
# rgb = accumulated emitted light, already depth-gated per emitter at
|
|
266
|
+
# stamp time (the glow stamp shader samples the depth rank mask and
|
|
267
|
+
# only lands light between the emitter's root-window surface and its
|
|
268
|
+
# own depth). Composite-side the light cancels shadow where it falls
|
|
269
|
+
# (glow_shadow_cut) then adds its emission on top (glow_strength).
|
|
270
|
+
'glow_map': (GLType.SAMPLER2D, None),
|
|
271
|
+
'glow_strength': (GLType.FLOAT, 0.0), # 0 disables the whole path
|
|
272
|
+
'glow_shadow_cut': (GLType.FLOAT, 1.0),
|
|
273
|
+
# Specular rim on the LIT edge of raised surfaces - the edge facing
|
|
274
|
+
# the light source (opposite side from the cast shadow). The shader
|
|
275
|
+
# marches the full-res depth mask toward the light: a lower surface
|
|
276
|
+
# within specular_bevel px marks the silhouette edge, and the pixel
|
|
277
|
+
# is shaded as if the edge were a quarter-round bevel of that
|
|
278
|
+
# radius. light_dir must match ShadowCast's so the highlight always
|
|
279
|
+
# sits opposite the shadow.
|
|
280
|
+
'light_dir': (GLType.VEC2, (-0.5, 1.6)),
|
|
281
|
+
# Bevel radius in px = width of the highlight rim, the apparent
|
|
282
|
+
# roundness of the edge. 0 disables the pass.
|
|
283
|
+
'specular_bevel': (GLType.FLOAT, 0.0),
|
|
284
|
+
# Microfacet-style roughness in (0, 1]: low = tight bright rim
|
|
285
|
+
# hugging the edge profile, high = broad dim sheen across the bevel.
|
|
286
|
+
'specular_roughness': (GLType.FLOAT, 0.4),
|
|
287
|
+
# Peak highlight strength added to the frame (white is).
|
|
288
|
+
'specular_strength': (GLType.FLOAT, 1.0),
|
|
289
|
+
# Fade of the highlight ALONG perpendicular lit edges, in px: brightness
|
|
290
|
+
# peaks at the owning window's lit corner (where the two lit edges
|
|
291
|
+
# meet) and runs out over this distance scanning away from it. The
|
|
292
|
+
# corner comes from CPU-side window geometry (win_mask/win_rects),
|
|
293
|
+
# interpolated analytically per fragment - perfectly smooth and it
|
|
294
|
+
# moves with the window. 0 = uniform rim.
|
|
295
|
+
'specular_fade': (GLType.FLOAT, 300.0),
|
|
296
|
+
# Adapts that fade length to the owning window's own size: per axis
|
|
297
|
+
# the fade runs over min(specular_fade, rel * edge_length). Big
|
|
298
|
+
# windows keep the fixed specular_fade look; small windows fade
|
|
299
|
+
# out within their own edge instead of holding a uniform bright
|
|
300
|
+
# rim. 0 disables the adaptation (pure fixed-f fade).
|
|
301
|
+
'specular_fade_rel': (GLType.FLOAT, 0.6),
|
|
302
|
+
# The frameless OS window's frame (titlebar/etc): its rect sits
|
|
303
|
+
# at frame_origin in the framebuffer, frame_size wide, corners of
|
|
304
|
+
# frame_radius, on a TRANSPARENT background. Inside that rounded rect the
|
|
305
|
+
# composite is the usual darkening; outside it - the shadow margin
|
|
306
|
+
# and the cut corners - there is nothing but the shadow, so the
|
|
307
|
+
# output becomes the shadow itself with premultiplied alpha
|
|
308
|
+
# (shadow_color·s, s). frame_size (0, 0) = no frame, the whole
|
|
309
|
+
# framebuffer is drawn.
|
|
310
|
+
'frame_origin': (GLType.VEC2, (0.0, 0.0)),
|
|
311
|
+
'frame_radius': (GLType.FLOAT, 0.0),
|
|
312
|
+
'frame_size': (GLType.VEC2, (0.0, 0.0)),
|
|
313
|
+
# Window-occlusion mask (blit_offscreen._build_window_mask): R16,
|
|
314
|
+
# each dispatched window's rounded rect stamped back-to-front at
|
|
315
|
+
# rank (i+1)/1024. The specular pass decodes the rank at a
|
|
316
|
+
# fragment to find its owning window...
|
|
317
|
+
'win_mask': (GLType.SAMPLER2D, None),
|
|
318
|
+
# ...and texelFetches that window's fb-space rect (x0, y0, x1, y1)
|
|
319
|
+
# from this 256x1 RGBA32F table, filled in the same z order.
|
|
320
|
+
'win_rects': (GLType.SAMPLER2D, None),
|
|
321
|
+
# Depth falloff of the highlight: intensity decays as
|
|
322
|
+
# exp(-receiver_depth * rate), so surfaces near the floor catch
|
|
323
|
+
# the full highlight and high-stacked ones progressively lose it.
|
|
324
|
+
# Depth is in depth_scale'd units (one layer slot ~ 0.3 at the
|
|
325
|
+
# default 64/32 layer/depth ratio). 0 = depth-independent.
|
|
326
|
+
'specular_depth_falloff': (GLType.FLOAT, 0.0),
|
|
327
|
+
# Minimum depth drop (in depth_scale'd units) that counts as a
|
|
328
|
+
# silhouette edge - rejects same-surface rasterization noise.
|
|
329
|
+
'specular_depth_eps': (GLType.FLOAT, 0.001),
|
|
330
|
+
# Per-px slope allowance added to that threshold as the march gets
|
|
331
|
+
# farther: a sample only reads as an edge when it sits more than
|
|
332
|
+
# eps + tol*distance below the start. Marks interpolate rank
|
|
333
|
+
# across their quad (per-corner values), so a single background
|
|
334
|
+
# can slope smoothly - this keeps a tilted surface from reading
|
|
335
|
+
# as a phantom edge (only the short bevel march remains, so the
|
|
336
|
+
# allowance rarely exceeds eps in practice).
|
|
337
|
+
'specular_slope_tol': (GLType.FLOAT, 0.0002),
|
|
338
|
+
'texture_size': (GLType.VEC2, None),
|
|
339
|
+
}
|
|
340
|
+
fragment_code = """
|
|
341
|
+
// Distance in px from `uv` to the nearest LOWER surface marching along
|
|
342
|
+
// `dir` (a unit, in pixels), up to max_d. Coarse 16-step march to bracket
|
|
343
|
+
// the edge, then a short bisection so long fades don't band into visible
|
|
344
|
+
// steps. Returns max_d when no edge is found in range.
|
|
345
|
+
float _spec_edge_dist(vec2 uv, vec2 dir, float d0, float max_d, vec2 texel) {
|
|
346
|
+
float lo = 0.0;
|
|
347
|
+
float hi = -1.0;
|
|
348
|
+
for (int i = 1; i <= 16; i++) {
|
|
349
|
+
float t = max_d * float(i) / 16.0;
|
|
350
|
+
float d_s = texture(depth_map, uv + dir * t * texel).r * depth_scale;
|
|
351
|
+
// Slope-relative threshold: marks interpolate depth across their
|
|
352
|
+
// quad, so a surface can tilt — only a DROP steeper than the
|
|
353
|
+
// per-px allowance counts as a silhouette edge.
|
|
354
|
+
if (d_s < d0 - specular_depth_eps - specular_slope_tol * t) {
|
|
355
|
+
hi = t; break;
|
|
356
|
+
}
|
|
357
|
+
lo = t;
|
|
358
|
+
}
|
|
359
|
+
if (hi < 0.0) return max_d;
|
|
360
|
+
for (int b = 0; b < 4; b++) {
|
|
361
|
+
float mid = 0.5 * (lo + hi);
|
|
362
|
+
float d_m = texture(depth_map, uv + dir * mid * texel).r * depth_scale;
|
|
363
|
+
if (d_m < d0 - specular_depth_eps - specular_slope_tol * mid) {
|
|
364
|
+
hi = mid;
|
|
365
|
+
} else {
|
|
366
|
+
lo = mid;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return hi;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Extended sRGB (mirrored for negatives, unbounded above) — hdr_color.py.
|
|
373
|
+
vec3 melty_encode(vec3 v) {
|
|
374
|
+
vec3 a = abs(v);
|
|
375
|
+
vec3 e = mix(a * 12.92, 1.055 * pow(a, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, a));
|
|
376
|
+
return sign(v) * e;
|
|
377
|
+
}
|
|
378
|
+
vec3 melty_decode(vec3 c) {
|
|
379
|
+
vec3 a = abs(c);
|
|
380
|
+
vec3 l = mix(a / 12.92, pow((a + 0.055) / 1.055, vec3(2.4)), step(0.04045, a));
|
|
381
|
+
return sign(c) * l;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
void main() {
|
|
385
|
+
vec2 uv = v_texcoord;
|
|
386
|
+
|
|
387
|
+
vec4 color = texture(u_texture, uv);
|
|
388
|
+
float depth = texture(depth_map, uv).r * depth_scale;
|
|
389
|
+
|
|
390
|
+
// Joint bilateral upsample of the (possibly low-res) shadow map. A plain
|
|
391
|
+
// bilinear fetch smears the shadow silhouette across the crisp rounded-rect
|
|
392
|
+
// edges, producing visible fringing. Instead we gather the four nearest
|
|
393
|
+
// low-res taps and weight each by (a) the usual bilinear weight and (b) how
|
|
394
|
+
// closely its receiver depth (stored in shadow.a by ShadowCast) matches the
|
|
395
|
+
// full-res depth at this pixel. Taps belonging to a different surface are
|
|
396
|
+
// rejected, so the shadow snaps back to the high-res geometry edge.
|
|
397
|
+
vec2 texel = 1.0 / shadow_size;
|
|
398
|
+
vec2 sample_pos = uv * shadow_size - 0.5;
|
|
399
|
+
vec2 base = floor(sample_pos);
|
|
400
|
+
vec2 frac = sample_pos - base;
|
|
401
|
+
|
|
402
|
+
float shadow_sum = 0.0;
|
|
403
|
+
float weight_sum = 0.0;
|
|
404
|
+
|
|
405
|
+
for (int x = 0; x <= 1; x++) {
|
|
406
|
+
for (int y = 0; y <= 1; y++) {
|
|
407
|
+
vec2 tap = (base + vec2(float(x), float(y)) + 0.5) * texel;
|
|
408
|
+
vec4 s = texture(shadow_map, tap);
|
|
409
|
+
|
|
410
|
+
float wx = (x == 0) ? (1.0 - frac.x) : frac.x;
|
|
411
|
+
float wy = (y == 0) ? (1.0 - frac.y) : frac.y;
|
|
412
|
+
float spatial = wx * wy;
|
|
413
|
+
|
|
414
|
+
float depth_weight = exp(-abs(s.a - depth) * depth_sharpness);
|
|
415
|
+
|
|
416
|
+
float w = spatial * depth_weight;
|
|
417
|
+
shadow_sum += s.r * w;
|
|
418
|
+
weight_sum += w;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Fall back to a straight bilinear fetch if every tap was rejected (e.g. a
|
|
423
|
+
// thin feature with no depth-matching neighbor) so we never punch a hole.
|
|
424
|
+
float shadow_intensity = (weight_sum > 0.0001)
|
|
425
|
+
? (shadow_sum / weight_sum)
|
|
426
|
+
: texture(shadow_map, uv).r;
|
|
427
|
+
|
|
428
|
+
// Glow rects as light sources: sample the low-res light buffer (bilinear
|
|
429
|
+
// sampler upscales the smooth falloff for free; depth gating already
|
|
430
|
+
// happened per emitter at stamp time). Light eats shadow first, then
|
|
431
|
+
// adds its own emission below.
|
|
432
|
+
vec3 glow_light = vec3(0.0);
|
|
433
|
+
if (glow_strength > 0.0) {
|
|
434
|
+
glow_light = texture(glow_map, uv).rgb;
|
|
435
|
+
float glum = dot(glow_light, vec3(0.299, 0.587, 0.114));
|
|
436
|
+
shadow_intensity *= max(0.0, 1.0 - glum * glow_shadow_cut);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// The scene is LINEAR scRGB (hdr_color.py) but every knob here —
|
|
440
|
+
// shadow_color, the opacities, the glow and specular strengths — was
|
|
441
|
+
// tuned against the sRGB-ENCODED frame, so the mix runs in that domain
|
|
442
|
+
// and decodes at the end: the same look, and a shadow_color of
|
|
443
|
+
// (0, 0.02, 0.05) stays the blue-black it reads as (taken as linear
|
|
444
|
+
// light it is sRGB (0, 0.15, 0.25): the blue shadows of 09-07).
|
|
445
|
+
// Extended curves: values above 1 and below 0 pass through.
|
|
446
|
+
vec3 color_enc = melty_encode(color.rgb);
|
|
447
|
+
vec3 shadowed = mix(color_enc, shadow_color, shadow_intensity * shadow_opacity);
|
|
448
|
+
shadowed += melty_encode(glow_light) * glow_strength;
|
|
449
|
+
|
|
450
|
+
// Specular bevel highlight on the lit edge. March toward the light in
|
|
451
|
+
// the full-res depth mask: the first sample that drops below this
|
|
452
|
+
// pixel's surface is the silhouette edge, so this pixel sits on the
|
|
453
|
+
// rim that faces the light (shadows are displaced the OPPOSITE way,
|
|
454
|
+
// toward -light_dir, so highlight and shadow stay consistent). Convex
|
|
455
|
+
// rounded corners fall out for free — the march follows the mask.
|
|
456
|
+
if (specular_bevel > 0.0) {
|
|
457
|
+
vec2 texel_full = 1.0 / texture_size;
|
|
458
|
+
vec2 light_n = normalize(light_dir);
|
|
459
|
+
float edge_t = _spec_edge_dist(uv, light_n, depth,
|
|
460
|
+
specular_bevel, texel_full);
|
|
461
|
+
if (edge_t < specular_bevel) {
|
|
462
|
+
// Quarter-round bevel of radius specular_bevel: at the edge
|
|
463
|
+
// the normal tilts fully toward the light, flattening to
|
|
464
|
+
// straight-up one bevel radius in.
|
|
465
|
+
float f = edge_t / specular_bevel; // 0 edge .. 1 flat
|
|
466
|
+
float sin_t = 1.0 - f;
|
|
467
|
+
float cos_t = sqrt(max(0.0, 1.0 - sin_t * sin_t));
|
|
468
|
+
vec3 n = vec3(light_n * sin_t, cos_t);
|
|
469
|
+
// Light at 45 deg elevation from the light_dir side, viewer
|
|
470
|
+
// straight down; Blinn half-vector with a roughness-driven
|
|
471
|
+
// exponent (rough = broad+dim, smooth = tight+bright).
|
|
472
|
+
vec3 L = normalize(vec3(light_n, 1.0));
|
|
473
|
+
vec3 H = normalize(L + vec3(0.0, 0.0, 1.0));
|
|
474
|
+
float rough = clamp(specular_roughness, 0.02, 1.0);
|
|
475
|
+
float shininess = max(1.0, 0.5 / (rough * rough));
|
|
476
|
+
float spec = pow(max(dot(n, H), 0.0), shininess);
|
|
477
|
+
spec *= 1.0 - 0.5 * rough;
|
|
478
|
+
|
|
479
|
+
// Fade along the edge: brightness peaks at the owning
|
|
480
|
+
// window's LIT corner and dies out scanning away from it
|
|
481
|
+
// along either edge. The corner comes from CPU-side window
|
|
482
|
+
// geometry: the win_mask rank at this fragment identifies the
|
|
483
|
+
// topmost window, win_rects holds its fb-space rect, and the
|
|
484
|
+
// fade is an analytic distance field from the rect's corner
|
|
485
|
+
// toward +light_dir. No depth-mask walks — the field is
|
|
486
|
+
// perfectly smooth and translates with the window, so it
|
|
487
|
+
// cannot dash, stair-step, or wobble as views move. Fragments
|
|
488
|
+
// no window owns (floor-level marks) keep fade = 1.
|
|
489
|
+
float fade = 1.0;
|
|
490
|
+
if (specular_fade > 0.0) {
|
|
491
|
+
float zn = texture(win_mask, uv).r;
|
|
492
|
+
if (zn > 0.0) {
|
|
493
|
+
int idx = clamp(int(round(zn * 1024.0)) - 1, 0, 255);
|
|
494
|
+
vec4 r = texelFetch(win_rects, ivec2(idx, 0), 0);
|
|
495
|
+
vec2 frag_px = uv / texel_full;
|
|
496
|
+
vec2 corner = vec2(
|
|
497
|
+
light_n.x > 0.0 ? r.z : r.x,
|
|
498
|
+
light_n.y > 0.0 ? r.w : r.y);
|
|
499
|
+
vec2 d = abs(frag_px - corner);
|
|
500
|
+
vec2 ext = max(r.zw - r.xy, vec2(1.0));
|
|
501
|
+
float u_max = 0.0;
|
|
502
|
+
if (abs(light_n.x) > 1e-3) {
|
|
503
|
+
float fl = (specular_fade_rel > 0.0)
|
|
504
|
+
? min(specular_fade, specular_fade_rel * ext.x)
|
|
505
|
+
: specular_fade;
|
|
506
|
+
u_max = max(u_max, d.x / max(fl, 1.0));
|
|
507
|
+
}
|
|
508
|
+
if (abs(light_n.y) > 1e-3) {
|
|
509
|
+
float fl = (specular_fade_rel > 0.0)
|
|
510
|
+
? min(specular_fade, specular_fade_rel * ext.y)
|
|
511
|
+
: specular_fade;
|
|
512
|
+
u_max = max(u_max, d.y / max(fl, 1.0));
|
|
513
|
+
}
|
|
514
|
+
fade = 1.0 - smoothstep(0.0, 1.0, u_max);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// Depth falloff: low surfaces (near the floor) keep the full
|
|
518
|
+
// highlight, high-stacked ones fade out exponentially.
|
|
519
|
+
if (specular_depth_falloff > 0.0) {
|
|
520
|
+
fade *= exp(-max(depth, 0.0) * specular_depth_falloff);
|
|
521
|
+
}
|
|
522
|
+
shadowed += vec3(spec * fade * specular_strength);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Frameless-window frame: coverage of the content's rounded rect at this
|
|
527
|
+
// pixel (gl_FragCoord: pixel centres, origin bottom-left — the rect is
|
|
528
|
+
// symmetric so no flip). Outside it only the shadow exists: emit it as
|
|
529
|
+
// premultiplied alpha so the compositor blends the desktop through.
|
|
530
|
+
float frame_cov = 1.0;
|
|
531
|
+
if (frame_size.x > 0.0 && frame_size.y > 0.0) {
|
|
532
|
+
vec2 half_size = frame_size * 0.5;
|
|
533
|
+
vec2 fd = abs(gl_FragCoord.xy - frame_origin - half_size) - (half_size - vec2(frame_radius));
|
|
534
|
+
float fdist = length(max(fd, vec2(0.0))) + min(max(fd.x, fd.y), 0.0) - frame_radius;
|
|
535
|
+
frame_cov = 1.0 - smoothstep(-0.5, 0.5, fdist);
|
|
536
|
+
}
|
|
537
|
+
float bg_alpha = shadow_intensity * shadow_opacity;
|
|
538
|
+
vec3 bg_rgb = melty_decode(shadow_color) * bg_alpha;
|
|
539
|
+
fragColor = vec4(mix(bg_rgb, melty_decode(shadowed), frame_cov), mix(bg_alpha, color.a, frame_cov));
|
|
540
|
+
}
|
|
541
|
+
"""
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
# =============================================================================
|
|
545
|
+
# SHADER TYPES
|
|
546
|
+
# =============================================================================
|
|
547
|
+
|
|
548
|
+
@register_shader_type
|
|
549
|
+
class Standard:
|
|
550
|
+
"""
|
|
551
|
+
Standard shader type for fullscreen texture processing.
|
|
552
|
+
|
|
553
|
+
Uses a simple quad with position and texture coordinates.
|
|
554
|
+
"""
|
|
555
|
+
vertex_in = {
|
|
556
|
+
'position': GLType.VEC3,
|
|
557
|
+
'texcoord': GLType.VEC2,
|
|
558
|
+
}
|
|
559
|
+
vertex_out = {
|
|
560
|
+
'v_texcoord': GLType.VEC2,
|
|
561
|
+
}
|
|
562
|
+
vertex_code = """
|
|
563
|
+
void main() {
|
|
564
|
+
v_texcoord = texcoord;
|
|
565
|
+
gl_Position = vec4(position, 1.0);
|
|
566
|
+
}
|
|
567
|
+
"""
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
@register_shader_type
|
|
571
|
+
class Fullscreen:
|
|
572
|
+
"""
|
|
573
|
+
Fullscreen shader type - generates texture coordinates from position.
|
|
574
|
+
|
|
575
|
+
Useful when you don't have explicit texture coordinates.
|
|
576
|
+
"""
|
|
577
|
+
vertex_in = {
|
|
578
|
+
'position': GLType.VEC3,
|
|
579
|
+
}
|
|
580
|
+
vertex_out = {
|
|
581
|
+
'v_texcoord': GLType.VEC2,
|
|
582
|
+
}
|
|
583
|
+
vertex_code = """
|
|
584
|
+
void main() {
|
|
585
|
+
v_texcoord = position.xy * 0.5 + 0.5;
|
|
586
|
+
gl_Position = vec4(position, 1.0);
|
|
587
|
+
}
|
|
588
|
+
"""
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
@register_shader_type
|
|
593
|
+
class Transformed:
|
|
594
|
+
"""
|
|
595
|
+
Shader type with model-view-projection transformation.
|
|
596
|
+
|
|
597
|
+
Useful for 3D scenes or when you need transforms.
|
|
598
|
+
"""
|
|
599
|
+
vertex_in = {
|
|
600
|
+
'position': GLType.VEC3,
|
|
601
|
+
'texcoord': GLType.VEC2,
|
|
602
|
+
}
|
|
603
|
+
vertex_out = {
|
|
604
|
+
'v_texcoord': GLType.VEC2,
|
|
605
|
+
}
|
|
606
|
+
vertex_code = """
|
|
607
|
+
void main() {
|
|
608
|
+
v_texcoord = texcoord;
|
|
609
|
+
gl_Position = u_projection * u_modelview * vec4(position, 1.0);
|
|
610
|
+
}
|
|
611
|
+
"""
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
# =============================================================================
|
|
615
|
+
# BASIC FILTERS
|
|
616
|
+
# =============================================================================
|
|
617
|
+
|
|
618
|
+
@register_shader
|
|
619
|
+
class Jet:
|
|
620
|
+
"""
|
|
621
|
+
Apply jet colormap to the red channel of a texture.
|
|
622
|
+
|
|
623
|
+
Maps input red values [0, 1] to the jet color scale
|
|
624
|
+
(blue -> cyan -> green -> yellow -> red).
|
|
625
|
+
|
|
626
|
+
Input uniforms:
|
|
627
|
+
u_texture: Input texture (uses red channel)
|
|
628
|
+
|
|
629
|
+
Output: RGB jet-colored representation of input intensity
|
|
630
|
+
"""
|
|
631
|
+
shader_type = 'standard'
|
|
632
|
+
uniforms = {
|
|
633
|
+
'offset': (GLType.FLOAT, 0.0), # -1.0 to 1.0
|
|
634
|
+
}
|
|
635
|
+
fragment_code = """
|
|
636
|
+
vec3 jet(float t) {
|
|
637
|
+
// Clamp input to [0, 1]
|
|
638
|
+
t += offset;
|
|
639
|
+
t = clamp(t, 0.0, 1.0);
|
|
640
|
+
|
|
641
|
+
// Compute RGB components using piecewise linear approximation
|
|
642
|
+
// Each channel is a trapezoid function centered at different positions
|
|
643
|
+
float r = clamp(1.5 - abs(t - 0.75) * 4.0, 0.0, 1.0);
|
|
644
|
+
float g = clamp(1.5 - abs(t - 0.5) * 4.0, 0.0, 1.0);
|
|
645
|
+
float b = clamp(1.5 - abs(t - 0.25) * 4.0, 0.0, 1.0);
|
|
646
|
+
|
|
647
|
+
return vec3(r, g, b);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
void main() {
|
|
651
|
+
float intensity = texture(u_texture, v_texcoord).r;
|
|
652
|
+
fragColor = vec4(jet(intensity), 1.0);
|
|
653
|
+
}
|
|
654
|
+
"""
|
|
655
|
+
|
|
656
|
+
@register_shader
|
|
657
|
+
class Passthrough:
|
|
658
|
+
"""Simple passthrough - copies input to output unchanged."""
|
|
659
|
+
shader_type = 'standard'
|
|
660
|
+
uniforms = {}
|
|
661
|
+
fragment_code = """
|
|
662
|
+
void main() {
|
|
663
|
+
fragColor = texture(u_texture, v_texcoord);
|
|
664
|
+
}
|
|
665
|
+
"""
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
@register_shader
|
|
669
|
+
class BrightnessContrast:
|
|
670
|
+
"""Adjust brightness and contrast of an image."""
|
|
671
|
+
shader_type = 'standard'
|
|
672
|
+
uniforms = {
|
|
673
|
+
'brightness': (GLType.FLOAT, 0.0), # -1.0 to 1.0
|
|
674
|
+
'contrast': (GLType.FLOAT, 1.0), # 0.0 to 2.0+
|
|
675
|
+
}
|
|
676
|
+
fragment_code = """
|
|
677
|
+
void main() {
|
|
678
|
+
vec4 color = texture(u_texture, v_texcoord);
|
|
679
|
+
// Apply contrast around midpoint, then add brightness
|
|
680
|
+
color.rgb = (color.rgb - 0.5) * contrast + 0.5 + brightness;
|
|
681
|
+
// No ceiling and no floor: the working space is linear scRGB
|
|
682
|
+
// (hdr_color.py) -- values above 1.0 are real highlights and NEGATIVE
|
|
683
|
+
// components are colours outside sRGB (a BT.2020 green is
|
|
684
|
+
// (-1.9, 3.6, -0.3)). Flooring them here collapsed every wide-gamut
|
|
685
|
+
// pixel to the sRGB gamut before the present pass could convert
|
|
686
|
+
// primaries, so HDR screenshots showed muted (09-10). The present
|
|
687
|
+
// pass clamps AFTER its primaries matrix, where it is safe.
|
|
688
|
+
fragColor = color;
|
|
689
|
+
}
|
|
690
|
+
"""
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
@register_shader
|
|
694
|
+
class HueSaturation:
|
|
695
|
+
"""Adjust hue, saturation, and lightness."""
|
|
696
|
+
shader_type = 'standard'
|
|
697
|
+
uniforms = {
|
|
698
|
+
'hue_shift': (GLType.FLOAT, 0.0), # -1.0 to 1.0 (maps to -180 to 180 degrees)
|
|
699
|
+
'saturation': (GLType.FLOAT, 1.0), # 0.0 to 2.0+
|
|
700
|
+
'lightness': (GLType.FLOAT, 0.0), # -1.0 to 1.0
|
|
701
|
+
}
|
|
702
|
+
fragment_code = """
|
|
703
|
+
vec3 rgb2hsl(vec3 c) {
|
|
704
|
+
float maxc = max(max(c.r, c.g), c.b);
|
|
705
|
+
float minc = min(min(c.r, c.g), c.b);
|
|
706
|
+
float l = (maxc + minc) / 2.0;
|
|
707
|
+
|
|
708
|
+
if (maxc == minc) {
|
|
709
|
+
return vec3(0.0, 0.0, l);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
float d = maxc - minc;
|
|
713
|
+
float s = l > 0.5 ? d / (2.0 - maxc - minc) : d / (maxc + minc);
|
|
714
|
+
|
|
715
|
+
float h;
|
|
716
|
+
if (maxc == c.r) {
|
|
717
|
+
h = (c.g - c.b) / d + (c.g < c.b ? 6.0 : 0.0);
|
|
718
|
+
} else if (maxc == c.g) {
|
|
719
|
+
h = (c.b - c.r) / d + 2.0;
|
|
720
|
+
} else {
|
|
721
|
+
h = (c.r - c.g) / d + 4.0;
|
|
722
|
+
}
|
|
723
|
+
h /= 6.0;
|
|
724
|
+
|
|
725
|
+
return vec3(h, s, l);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
float hue2rgb(float p, float q, float t) {
|
|
729
|
+
if (t < 0.0) t += 1.0;
|
|
730
|
+
if (t > 1.0) t -= 1.0;
|
|
731
|
+
if (t < 1.0/6.0) return p + (q - p) * 6.0 * t;
|
|
732
|
+
if (t < 1.0/2.0) return q;
|
|
733
|
+
if (t < 2.0/3.0) return p + (q - p) * (2.0/3.0 - t) * 6.0;
|
|
734
|
+
return p;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
vec3 hsl2rgb(vec3 c) {
|
|
738
|
+
if (c.y == 0.0) {
|
|
739
|
+
return vec3(c.z);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
float q = c.z < 0.5 ? c.z * (1.0 + c.y) : c.z + c.y - c.z * c.y;
|
|
743
|
+
float p = 2.0 * c.z - q;
|
|
744
|
+
|
|
745
|
+
return vec3(
|
|
746
|
+
hue2rgb(p, q, c.x + 1.0/3.0),
|
|
747
|
+
hue2rgb(p, q, c.x),
|
|
748
|
+
hue2rgb(p, q, c.x - 1.0/3.0)
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// sRGB <-> BT.2020 primaries (linear, D65; column-major, same numbers as
|
|
753
|
+
// hdr_color.MELTY_SRGB_TO_BT2020). HSL is done in BT.2020 because it
|
|
754
|
+
// contains everything a panel shows (P3 included): a wide colour is scRGB
|
|
755
|
+
// with NEGATIVE components (hdr_color.py) and HSL cannot take those. The
|
|
756
|
+
// old max(rgb, 0) collapsed such pixels to the sRGB gamut, so HDR
|
|
757
|
+
// screenshots showed muted even at saturation 1 (09-10).
|
|
758
|
+
const mat3 SRGB_TO_BT2020 = mat3(
|
|
759
|
+
0.6274039, 0.0690973, 0.0163914,
|
|
760
|
+
0.3292830, 0.9195404, 0.0880133,
|
|
761
|
+
0.0433131, 0.0113623, 0.8955953);
|
|
762
|
+
const mat3 BT2020_TO_SRGB = mat3(
|
|
763
|
+
1.6604910, -0.1245505, -0.0181508,
|
|
764
|
+
-0.5876411, 1.1328999, -0.1005789,
|
|
765
|
+
-0.0728499, -0.0083494, 1.1187297);
|
|
766
|
+
|
|
767
|
+
void main() {
|
|
768
|
+
vec4 color = texture(u_texture, v_texcoord);
|
|
769
|
+
if (hue_shift == 0.0 && saturation == 1.0 && lightness == 0.0) {
|
|
770
|
+
fragColor = color; // identity: bit-exact passthrough
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
vec3 wide = max(SRGB_TO_BT2020 * color.rgb, 0.0);
|
|
774
|
+
// HSL is defined on [0, 1]; an HDR pixel (a component above 1.0) is
|
|
775
|
+
// normalised by its brightest component first and scaled back after,
|
|
776
|
+
// so highlights keep their level through the edit.
|
|
777
|
+
float scale = max(max(max(wide.r, wide.g), wide.b), 1.0);
|
|
778
|
+
vec3 hsl = rgb2hsl(wide / scale);
|
|
779
|
+
|
|
780
|
+
// Apply adjustments
|
|
781
|
+
hsl.x = fract(hsl.x + hue_shift * 0.5); // Hue shift
|
|
782
|
+
hsl.y = clamp(hsl.y * saturation, 0.0, 1.0); // Saturation
|
|
783
|
+
hsl.z = clamp(hsl.z + lightness, 0.0, 1.0); // Lightness
|
|
784
|
+
|
|
785
|
+
fragColor = vec4(BT2020_TO_SRGB * (hsl2rgb(hsl) * scale), color.a);
|
|
786
|
+
}
|
|
787
|
+
"""
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
@register_shader
|
|
791
|
+
class Grayscale:
|
|
792
|
+
"""Convert to grayscale with adjustable method."""
|
|
793
|
+
shader_type = 'standard'
|
|
794
|
+
uniforms = {
|
|
795
|
+
'method': (GLType.INT, 0), # 0=luminance, 1=average, 2=lightness
|
|
796
|
+
}
|
|
797
|
+
fragment_code = """
|
|
798
|
+
void main() {
|
|
799
|
+
vec4 color = texture(u_texture, v_texcoord);
|
|
800
|
+
float gray;
|
|
801
|
+
|
|
802
|
+
if (method == 0) {
|
|
803
|
+
// Luminance (perceptual)
|
|
804
|
+
gray = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
|
|
805
|
+
} else if (method == 1) {
|
|
806
|
+
// Average
|
|
807
|
+
gray = (color.r + color.g + color.b) / 3.0;
|
|
808
|
+
} else {
|
|
809
|
+
// Lightness
|
|
810
|
+
gray = (max(max(color.r, color.g), color.b) + min(min(color.r, color.g), color.b)) / 2.0;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
fragColor = vec4(vec3(gray), color.a);
|
|
814
|
+
}
|
|
815
|
+
"""
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
@register_shader
|
|
819
|
+
class Invert:
|
|
820
|
+
"""Invert colors."""
|
|
821
|
+
shader_type = 'standard'
|
|
822
|
+
uniforms = {
|
|
823
|
+
'amount': (GLType.FLOAT, 1.0), # 0.0 to 1.0
|
|
824
|
+
}
|
|
825
|
+
fragment_code = """
|
|
826
|
+
void main() {
|
|
827
|
+
vec4 color = texture(u_texture, v_texcoord);
|
|
828
|
+
vec3 inverted = 1.0 - color.rgb;
|
|
829
|
+
fragColor = vec4(mix(color.rgb, inverted, amount), color.a);
|
|
830
|
+
}
|
|
831
|
+
"""
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
@register_shader
|
|
835
|
+
class Gamma:
|
|
836
|
+
"""Apply gamma correction."""
|
|
837
|
+
shader_type = 'standard'
|
|
838
|
+
uniforms = {
|
|
839
|
+
'gamma': (GLType.FLOAT, 1.0), # 0.1 to 3.0+
|
|
840
|
+
}
|
|
841
|
+
fragment_code = """
|
|
842
|
+
void main() {
|
|
843
|
+
vec4 color = texture(u_texture, v_texcoord);
|
|
844
|
+
color.rgb = pow(color.rgb, vec3(1.0 / gamma));
|
|
845
|
+
fragColor = color;
|
|
846
|
+
}
|
|
847
|
+
"""
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
@register_shader
|
|
851
|
+
class Threshold:
|
|
852
|
+
"""Apply threshold to create binary image."""
|
|
853
|
+
shader_type = 'standard'
|
|
854
|
+
uniforms = {
|
|
855
|
+
'threshold': (GLType.FLOAT, 0.5), # 0.0 to 1.0
|
|
856
|
+
'smoothness': (GLType.FLOAT, 0.0), # 0.0 = hard, higher = softer
|
|
857
|
+
}
|
|
858
|
+
fragment_code = """
|
|
859
|
+
void main() {
|
|
860
|
+
vec4 color = texture(u_texture, v_texcoord);
|
|
861
|
+
float lum = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
|
|
862
|
+
float value = smoothstep(threshold - smoothness, threshold + smoothness, lum);
|
|
863
|
+
fragColor = vec4(vec3(value), color.a);
|
|
864
|
+
}
|
|
865
|
+
"""
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
# =============================================================================
|
|
869
|
+
# BLUR FILTERS
|
|
870
|
+
# =============================================================================
|
|
871
|
+
|
|
872
|
+
@register_shader
|
|
873
|
+
class BoxBlur:
|
|
874
|
+
"""Simple box blur (uniform weighted average)."""
|
|
875
|
+
shader_type = 'standard'
|
|
876
|
+
uniforms = {
|
|
877
|
+
'radius': (GLType.FLOAT, 1.0),
|
|
878
|
+
'texture_size': (GLType.VEC2, (512.0, 512.0)),
|
|
879
|
+
}
|
|
880
|
+
fragment_code = """
|
|
881
|
+
void main() {
|
|
882
|
+
vec2 texel = 1.0 / texture_size;
|
|
883
|
+
vec4 sum = vec4(0.0);
|
|
884
|
+
int r = int(radius);
|
|
885
|
+
float count = 0.0;
|
|
886
|
+
|
|
887
|
+
for (int x = -r; x <= r; x++) {
|
|
888
|
+
for (int y = -r; y <= r; y++) {
|
|
889
|
+
sum += texture(u_texture, v_texcoord + vec2(float(x), float(y)) * texel);
|
|
890
|
+
count += 1.0;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
fragColor = sum / count;
|
|
895
|
+
}
|
|
896
|
+
"""
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
@register_shader
|
|
900
|
+
class GaussianBlurH:
|
|
901
|
+
"""Horizontal pass of separable Gaussian blur."""
|
|
902
|
+
shader_type = 'standard'
|
|
903
|
+
uniforms = {
|
|
904
|
+
'radius': (GLType.FLOAT, 4.0),
|
|
905
|
+
'texture_size': (GLType.VEC2, (512.0, 512.0)),
|
|
906
|
+
}
|
|
907
|
+
fragment_code = """
|
|
908
|
+
void main() {
|
|
909
|
+
vec2 texel = vec2(1.0 / texture_size.x, 0.0);
|
|
910
|
+
|
|
911
|
+
// Pre-computed Gaussian weights for sigma = radius/3
|
|
912
|
+
float weights[9];
|
|
913
|
+
weights[0] = 0.0625; weights[1] = 0.125; weights[2] = 0.1875;
|
|
914
|
+
weights[3] = 0.25; weights[4] = 0.1875; weights[5] = 0.125;
|
|
915
|
+
weights[6] = 0.0625; weights[7] = 0.0; weights[8] = 0.0;
|
|
916
|
+
|
|
917
|
+
vec4 sum = vec4(0.0);
|
|
918
|
+
float total = 0.0;
|
|
919
|
+
int r = int(radius);
|
|
920
|
+
|
|
921
|
+
for (int i = -r; i <= r; i++) {
|
|
922
|
+
float w = exp(-float(i*i) / (2.0 * radius * radius / 9.0));
|
|
923
|
+
sum += texture(u_texture, v_texcoord + float(i) * texel) * w;
|
|
924
|
+
total += w;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
fragColor = sum / total;
|
|
928
|
+
}
|
|
929
|
+
"""
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
@register_shader
|
|
933
|
+
class GaussianBlurV:
|
|
934
|
+
"""Vertical pass of separable Gaussian blur."""
|
|
935
|
+
shader_type = 'standard'
|
|
936
|
+
uniforms = {
|
|
937
|
+
'radius': (GLType.FLOAT, 4.0),
|
|
938
|
+
'texture_size': (GLType.VEC2, (512.0, 512.0)),
|
|
939
|
+
}
|
|
940
|
+
fragment_code = """
|
|
941
|
+
void main() {
|
|
942
|
+
vec2 texel = vec2(0.0, 1.0 / texture_size.y);
|
|
943
|
+
|
|
944
|
+
vec4 sum = vec4(0.0);
|
|
945
|
+
float total = 0.0;
|
|
946
|
+
int r = int(radius);
|
|
947
|
+
|
|
948
|
+
for (int i = -r; i <= r; i++) {
|
|
949
|
+
float w = exp(-float(i*i) / (2.0 * radius * radius / 9.0));
|
|
950
|
+
sum += texture(u_texture, v_texcoord + float(i) * texel) * w;
|
|
951
|
+
total += w;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
fragColor = sum / total;
|
|
955
|
+
}
|
|
956
|
+
"""
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
@register_shader
|
|
960
|
+
class GaussianBlur:
|
|
961
|
+
"""Combined Gaussian blur (both horizontal and vertical in one pass)."""
|
|
962
|
+
shader_type = 'standard'
|
|
963
|
+
uniforms = {
|
|
964
|
+
'radius': (GLType.FLOAT, 4.0),
|
|
965
|
+
'texture_size': (GLType.VEC2, None),
|
|
966
|
+
}
|
|
967
|
+
fragment_code = """
|
|
968
|
+
void main() {
|
|
969
|
+
vec2 texel = 1.0 / texture_size;
|
|
970
|
+
|
|
971
|
+
vec4 sum = vec4(0.0);
|
|
972
|
+
float total = 0.0;
|
|
973
|
+
int r = int(radius);
|
|
974
|
+
|
|
975
|
+
// 2D Gaussian blur - sample in both directions
|
|
976
|
+
for (int x = -r; x <= r; x++) {
|
|
977
|
+
for (int y = -r; y <= r; y++) {
|
|
978
|
+
float dist = float(x*x + y*y);
|
|
979
|
+
float w = exp(-dist / (2.0 * radius * radius / 9.0));
|
|
980
|
+
sum += texture(u_texture, v_texcoord + vec2(float(x), float(y)) * texel) * w;
|
|
981
|
+
total += w;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
fragColor = sum / total;
|
|
986
|
+
}
|
|
987
|
+
"""
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
# =============================================================================
|
|
991
|
+
# EDGE DETECTION
|
|
992
|
+
# =============================================================================
|
|
993
|
+
|
|
994
|
+
@register_shader
|
|
995
|
+
class Sobel:
|
|
996
|
+
"""Sobel edge detection."""
|
|
997
|
+
shader_type = 'standard'
|
|
998
|
+
uniforms = {
|
|
999
|
+
'texture_size': (GLType.VEC2, (512.0, 512.0)),
|
|
1000
|
+
'strength': (GLType.FLOAT, 1.0),
|
|
1001
|
+
}
|
|
1002
|
+
fragment_code = """
|
|
1003
|
+
void main() {
|
|
1004
|
+
vec2 texel = 1.0 / texture_size;
|
|
1005
|
+
|
|
1006
|
+
// Sample 3x3 neighborhood
|
|
1007
|
+
float tl = dot(texture(u_texture, v_texcoord + vec2(-1, -1) * texel).rgb, vec3(0.299, 0.587, 0.114));
|
|
1008
|
+
float t = dot(texture(u_texture, v_texcoord + vec2( 0, -1) * texel).rgb, vec3(0.299, 0.587, 0.114));
|
|
1009
|
+
float tr = dot(texture(u_texture, v_texcoord + vec2( 1, -1) * texel).rgb, vec3(0.299, 0.587, 0.114));
|
|
1010
|
+
float l = dot(texture(u_texture, v_texcoord + vec2(-1, 0) * texel).rgb, vec3(0.299, 0.587, 0.114));
|
|
1011
|
+
float r = dot(texture(u_texture, v_texcoord + vec2( 1, 0) * texel).rgb, vec3(0.299, 0.587, 0.114));
|
|
1012
|
+
float bl = dot(texture(u_texture, v_texcoord + vec2(-1, 1) * texel).rgb, vec3(0.299, 0.587, 0.114));
|
|
1013
|
+
float b = dot(texture(u_texture, v_texcoord + vec2( 0, 1) * texel).rgb, vec3(0.299, 0.587, 0.114));
|
|
1014
|
+
float br = dot(texture(u_texture, v_texcoord + vec2( 1, 1) * texel).rgb, vec3(0.299, 0.587, 0.114));
|
|
1015
|
+
|
|
1016
|
+
// Sobel kernels
|
|
1017
|
+
float gx = -tl - 2.0*l - bl + tr + 2.0*r + br;
|
|
1018
|
+
float gy = -tl - 2.0*t - tr + bl + 2.0*b + br;
|
|
1019
|
+
|
|
1020
|
+
float edge = sqrt(gx*gx + gy*gy) * strength;
|
|
1021
|
+
fragColor = vec4(vec3(edge), 1.0);
|
|
1022
|
+
}
|
|
1023
|
+
"""
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
@register_shader
|
|
1027
|
+
class Laplacian:
|
|
1028
|
+
"""Laplacian edge detection."""
|
|
1029
|
+
shader_type = 'standard'
|
|
1030
|
+
uniforms = {
|
|
1031
|
+
'texture_size': (GLType.VEC2, (512.0, 512.0)),
|
|
1032
|
+
'strength': (GLType.FLOAT, 1.0),
|
|
1033
|
+
}
|
|
1034
|
+
fragment_code = """
|
|
1035
|
+
void main() {
|
|
1036
|
+
vec2 texel = 1.0 / texture_size;
|
|
1037
|
+
|
|
1038
|
+
vec4 center = texture(u_texture, v_texcoord);
|
|
1039
|
+
vec4 t = texture(u_texture, v_texcoord + vec2(0, -1) * texel);
|
|
1040
|
+
vec4 b = texture(u_texture, v_texcoord + vec2(0, 1) * texel);
|
|
1041
|
+
vec4 l = texture(u_texture, v_texcoord + vec2(-1, 0) * texel);
|
|
1042
|
+
vec4 r = texture(u_texture, v_texcoord + vec2( 1, 0) * texel);
|
|
1043
|
+
|
|
1044
|
+
vec4 edge = (t + b + l + r - 4.0 * center) * strength;
|
|
1045
|
+
fragColor = vec4(abs(edge.rgb), center.a);
|
|
1046
|
+
}
|
|
1047
|
+
"""
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
# =============================================================================
|
|
1051
|
+
# COLOR EFFECTS
|
|
1052
|
+
# =============================================================================
|
|
1053
|
+
|
|
1054
|
+
@register_shader
|
|
1055
|
+
class Sepia:
|
|
1056
|
+
"""Apply sepia tone effect."""
|
|
1057
|
+
shader_type = 'standard'
|
|
1058
|
+
uniforms = {
|
|
1059
|
+
'amount': (GLType.FLOAT, 1.0),
|
|
1060
|
+
}
|
|
1061
|
+
fragment_code = """
|
|
1062
|
+
void main() {
|
|
1063
|
+
vec4 color = texture(u_texture, v_texcoord);
|
|
1064
|
+
|
|
1065
|
+
vec3 sepia;
|
|
1066
|
+
sepia.r = dot(color.rgb, vec3(0.393, 0.769, 0.189));
|
|
1067
|
+
sepia.g = dot(color.rgb, vec3(0.349, 0.686, 0.168));
|
|
1068
|
+
sepia.b = dot(color.rgb, vec3(0.272, 0.534, 0.131));
|
|
1069
|
+
|
|
1070
|
+
fragColor = vec4(mix(color.rgb, sepia, amount), color.a);
|
|
1071
|
+
}
|
|
1072
|
+
"""
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
@register_shader
|
|
1076
|
+
class Vignette:
|
|
1077
|
+
"""Apply vignette effect (darkened edges)."""
|
|
1078
|
+
shader_type = 'standard'
|
|
1079
|
+
uniforms = {
|
|
1080
|
+
'radius': (GLType.FLOAT, 0.75), # Inner radius where vignette starts
|
|
1081
|
+
'softness': (GLType.FLOAT, 0.45), # Softness of the vignette
|
|
1082
|
+
'amount': (GLType.FLOAT, 0.5), # Darkness amount
|
|
1083
|
+
}
|
|
1084
|
+
fragment_code = """
|
|
1085
|
+
void main() {
|
|
1086
|
+
vec4 color = texture(u_texture, v_texcoord);
|
|
1087
|
+
|
|
1088
|
+
vec2 center = v_texcoord - 0.5;
|
|
1089
|
+
float dist = length(center) * 1.414; // Normalize to corners = 1
|
|
1090
|
+
|
|
1091
|
+
float vignette = smoothstep(radius, radius - softness, dist);
|
|
1092
|
+
color.rgb = mix(color.rgb * (1.0 - amount), color.rgb, vignette);
|
|
1093
|
+
|
|
1094
|
+
fragColor = color;
|
|
1095
|
+
}
|
|
1096
|
+
"""
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
@register_shader
|
|
1100
|
+
class ChromaticAberration:
|
|
1101
|
+
"""Simulate chromatic aberration (color fringing)."""
|
|
1102
|
+
shader_type = 'standard'
|
|
1103
|
+
uniforms = {
|
|
1104
|
+
'amount': (GLType.FLOAT, 0.005),
|
|
1105
|
+
}
|
|
1106
|
+
fragment_code = """
|
|
1107
|
+
void main() {
|
|
1108
|
+
vec2 direction = v_texcoord - 0.5;
|
|
1109
|
+
|
|
1110
|
+
float r = texture(u_texture, v_texcoord + direction * amount).r;
|
|
1111
|
+
float g = texture(u_texture, v_texcoord).g;
|
|
1112
|
+
float b = texture(u_texture, v_texcoord - direction * amount).b;
|
|
1113
|
+
float a = texture(u_texture, v_texcoord).a;
|
|
1114
|
+
|
|
1115
|
+
fragColor = vec4(r, g, b, a);
|
|
1116
|
+
}
|
|
1117
|
+
"""
|
|
1118
|
+
|
|
1119
|
+
|
|
1120
|
+
@register_shader
|
|
1121
|
+
class Posterize:
|
|
1122
|
+
"""Reduce color levels for a poster effect."""
|
|
1123
|
+
shader_type = 'standard'
|
|
1124
|
+
uniforms = {
|
|
1125
|
+
'levels': (GLType.FLOAT, 8.0), # Number of levels per channel
|
|
1126
|
+
}
|
|
1127
|
+
fragment_code = """
|
|
1128
|
+
void main() {
|
|
1129
|
+
vec4 color = texture(u_texture, v_texcoord);
|
|
1130
|
+
color.rgb = floor(color.rgb * levels + 0.5) / levels;
|
|
1131
|
+
fragColor = color;
|
|
1132
|
+
}
|
|
1133
|
+
"""
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
# =============================================================================
|
|
1137
|
+
# DISTORTION
|
|
1138
|
+
# =============================================================================
|
|
1139
|
+
|
|
1140
|
+
@register_shader
|
|
1141
|
+
class Pixelate:
|
|
1142
|
+
"""Pixelate effect."""
|
|
1143
|
+
shader_type = 'standard'
|
|
1144
|
+
uniforms = {
|
|
1145
|
+
'pixel_size': (GLType.FLOAT, 8.0),
|
|
1146
|
+
'texture_size': (GLType.VEC2, (512.0, 512.0)),
|
|
1147
|
+
}
|
|
1148
|
+
fragment_code = """
|
|
1149
|
+
void main() {
|
|
1150
|
+
vec2 size = pixel_size / texture_size;
|
|
1151
|
+
vec2 coord = floor(v_texcoord / size) * size + size * 0.5;
|
|
1152
|
+
fragColor = texture(u_texture, coord);
|
|
1153
|
+
}
|
|
1154
|
+
"""
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
@register_shader
|
|
1158
|
+
class Swirl:
|
|
1159
|
+
"""Swirl distortion effect."""
|
|
1160
|
+
shader_type = 'standard'
|
|
1161
|
+
uniforms = {
|
|
1162
|
+
'angle': (GLType.FLOAT, 3.0), # Swirl angle
|
|
1163
|
+
'radius': (GLType.FLOAT, 0.5), # Effect radius
|
|
1164
|
+
'center': (GLType.VEC2, (0.5, 0.5)),
|
|
1165
|
+
}
|
|
1166
|
+
fragment_code = """
|
|
1167
|
+
void main() {
|
|
1168
|
+
vec2 coord = v_texcoord - center;
|
|
1169
|
+
float dist = length(coord);
|
|
1170
|
+
|
|
1171
|
+
if (dist < radius) {
|
|
1172
|
+
float percent = (radius - dist) / radius;
|
|
1173
|
+
float theta = percent * percent * angle;
|
|
1174
|
+
float s = sin(theta);
|
|
1175
|
+
float c = cos(theta);
|
|
1176
|
+
coord = vec2(
|
|
1177
|
+
coord.x * c - coord.y * s,
|
|
1178
|
+
coord.x * s + coord.y * c
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
fragColor = texture(u_texture, coord + center);
|
|
1183
|
+
}
|
|
1184
|
+
"""
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
@register_shader
|
|
1188
|
+
class Bulge:
|
|
1189
|
+
"""Bulge/pinch distortion effect."""
|
|
1190
|
+
shader_type = 'standard'
|
|
1191
|
+
uniforms = {
|
|
1192
|
+
'strength': (GLType.FLOAT, 0.5), # Positive = bulge, negative = pinch
|
|
1193
|
+
'radius': (GLType.FLOAT, 0.5),
|
|
1194
|
+
'center': (GLType.VEC2, (0.5, 0.5)),
|
|
1195
|
+
}
|
|
1196
|
+
fragment_code = """
|
|
1197
|
+
void main() {
|
|
1198
|
+
vec2 coord = v_texcoord - center;
|
|
1199
|
+
float dist = length(coord);
|
|
1200
|
+
|
|
1201
|
+
if (dist < radius) {
|
|
1202
|
+
float percent = dist / radius;
|
|
1203
|
+
float distortion = pow(percent, 1.0 - strength);
|
|
1204
|
+
coord = coord * distortion;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
fragColor = texture(u_texture, coord + center);
|
|
1208
|
+
}
|
|
1209
|
+
"""
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
# =============================================================================
|
|
1213
|
+
# NORMALIZATION
|
|
1214
|
+
# =============================================================================
|
|
1215
|
+
|
|
1216
|
+
@register_shader
|
|
1217
|
+
class MinMaxReduction:
|
|
1218
|
+
"""
|
|
1219
|
+
Reduction shader for finding min/max values in a texture.
|
|
1220
|
+
|
|
1221
|
+
Each output pixel represents the min/max of a 2x2 block from the input.
|
|
1222
|
+
This is used internally by the normalize() method for parallel reduction.
|
|
1223
|
+
|
|
1224
|
+
Output format: R=min_value, G=max_value (across all RGB channels)
|
|
1225
|
+
"""
|
|
1226
|
+
shader_type = 'standard'
|
|
1227
|
+
uniforms = {
|
|
1228
|
+
'texture_size': (GLType.VEC2, None),
|
|
1229
|
+
}
|
|
1230
|
+
fragment_code = """
|
|
1231
|
+
void main() {
|
|
1232
|
+
vec2 texel = 1.0 / texture_size;
|
|
1233
|
+
|
|
1234
|
+
// Sample 2x2 block
|
|
1235
|
+
vec3 s00 = texture(u_texture, v_texcoord + vec2(0.0, 0.0) * texel).rgb;
|
|
1236
|
+
vec3 s10 = texture(u_texture, v_texcoord + vec2(1.0, 0.0) * texel).rgb;
|
|
1237
|
+
vec3 s01 = texture(u_texture, v_texcoord + vec2(0.0, 1.0) * texel).rgb;
|
|
1238
|
+
vec3 s11 = texture(u_texture, v_texcoord + vec2(1.0, 1.0) * texel).rgb;
|
|
1239
|
+
|
|
1240
|
+
// Find min and max across all channels in the 2x2 block
|
|
1241
|
+
float min_val = min(min(min(s00.r, s00.g), min(s00.b, s10.r)),
|
|
1242
|
+
min(min(s10.g, s10.b), min(s01.r, s01.g)));
|
|
1243
|
+
min_val = min(min_val, min(min(s01.b, s11.r), min(s11.g, s11.b)));
|
|
1244
|
+
|
|
1245
|
+
float max_val = max(max(max(s00.r, s00.g), max(s00.b, s10.r)),
|
|
1246
|
+
max(max(s10.g, s10.b), max(s01.r, s01.g)));
|
|
1247
|
+
max_val = max(max_val, max(max(s01.b, s11.r), max(s11.g, s11.b)));
|
|
1248
|
+
|
|
1249
|
+
// Store min in R, max in G
|
|
1250
|
+
fragColor = vec4(min_val, max_val, 0.0, 1.0);
|
|
1251
|
+
}
|
|
1252
|
+
"""
|
|
1253
|
+
|
|
1254
|
+
|
|
1255
|
+
@register_shader
|
|
1256
|
+
class NormalizeRemap:
|
|
1257
|
+
"""
|
|
1258
|
+
Remaps texture values from [min, max] to [0, 1].
|
|
1259
|
+
|
|
1260
|
+
Used internally by the normalize() method after min/max calculation.
|
|
1261
|
+
"""
|
|
1262
|
+
shader_type = 'standard'
|
|
1263
|
+
uniforms = {
|
|
1264
|
+
# (min, max) live in the .r/.g of a 1x1 texture stored on the GPU by
|
|
1265
|
+
# texture_min_max - sampled here so the range is read straight from the
|
|
1266
|
+
# GPU with no CPU readback.
|
|
1267
|
+
'min_max_tex': (GLType.SAMPLER2D, None),
|
|
1268
|
+
}
|
|
1269
|
+
fragment_code = """
|
|
1270
|
+
void main() {
|
|
1271
|
+
vec4 color = texture(u_texture, v_texcoord);
|
|
1272
|
+
|
|
1273
|
+
vec2 mm = texelFetch(min_max_tex, ivec2(0, 0), 0).rg;
|
|
1274
|
+
float min_value = mm.x;
|
|
1275
|
+
float max_value = mm.y;
|
|
1276
|
+
|
|
1277
|
+
// Avoid division by zero
|
|
1278
|
+
float range = max_value - min_value;
|
|
1279
|
+
if (abs(range) < 1e-20) range = 1.0;
|
|
1280
|
+
|
|
1281
|
+
// Remap from [min, max] to [0, 1]
|
|
1282
|
+
color.rgb = (color.rgb - min_value) / range;
|
|
1283
|
+
|
|
1284
|
+
fragColor = color;
|
|
1285
|
+
}
|
|
1286
|
+
"""
|
|
1287
|
+
|
|
1288
|
+
|
|
1289
|
+
@register_shader
|
|
1290
|
+
class Multiply:
|
|
1291
|
+
"""Scale every colour channel by `factor` (alpha untouched): a plain
|
|
1292
|
+
exposure change, the dim behind draw_texture's crop selection."""
|
|
1293
|
+
shader_type = 'standard'
|
|
1294
|
+
uniforms = {
|
|
1295
|
+
'factor': (GLType.FLOAT, 1.0),
|
|
1296
|
+
}
|
|
1297
|
+
fragment_code = """
|
|
1298
|
+
void main() {
|
|
1299
|
+
vec4 color = texture(u_texture, v_texcoord);
|
|
1300
|
+
fragColor = vec4(color.rgb * factor, color.a);
|
|
1301
|
+
}
|
|
1302
|
+
"""
|