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